Skip to Content

Customer mapping

StagingCustomer is intentionally minimal — flat name and contact fields, one address (not an array), and two boolean flags for marketing consent. Anything more goes in dict_metafields.

Quick reference — minimum loadable customer

{ "original_id": entity_id, "email": email, "firstName": firstname, "lastName": lastname }

A customer needs at minimum an email or phone to be useful (those are the login identities). Neither is enforced by the staging model — enforce in your mapping if your source allows blanks.

Identity

Login fields and display name.

emailstring

The customer’s email. Unique per shop. Used as the primary login identity.

"email": email
phonestring

E.164 format (+15555550123). Shopify will reject loosely-formatted phones like (555) 123-4567 — normalize before emitting.

"phone": "+" & country_dial_code & $replace(phone, /[^0-9]/, "")
firstNamestring
lastNamestring
localestring

The customer’s preferred language locale, e.g. en, fr-CA. Determines what language Shopify uses for transactional emails.

Address

One flat address. For multiple addresses on a single customer, store the extras in dict_metafields.

address1string

Street address line 1. Setting this is what triggers an addresses entry on the Shopify customer — without address1, no address is created.

"address1": billing.street
address2string

Apartment, suite, unit, etc.

citystring
provincestring

State / province name or code. Shopify accepts both.

countrystring

Country name or ISO 3166-1 alpha-2 code (e.g. US, DE). Codes are safer.

"country": country_id
zipstring

Postal code.

Marketing consent

Two booleans. Only set true if the source can prove the customer opted in.

accepts_email_marketingboolean

If true, sets the customer’s email marketing consent state to SUBSCRIBED. Has no effect unless email is also set.

"accepts_email_marketing": newsletter_subscribed = true
accepts_sms_marketingboolean

If true, sets SMS marketing consent to SUBSCRIBED. Requires phone to be set.

Customer attributes

Tax exempt, internal notes, tags.

tagsstring[] | string

Searchable tags. Either an array of strings or a single comma-separated string.

"tags": $append(customer_groups.name, ["lifetime-value-" & $string($floor(ltv / 100) * 100)])
taxExemptboolean

Whether the customer is tax-exempt (e.g. wholesale customer with a resale certificate).

notestring

Internal admin note. Not shown to the customer.

Patterns

Magento customer with billing address

{ "original_id": entity_id, "email": email, "firstName": firstname, "lastName": lastname, "phone": telephone, "address1": default_billing.street, "address2": default_billing.street_2, "city": default_billing.city, "province": default_billing.region.region_code, "country": default_billing.country_id, "zip": default_billing.postcode, "accepts_email_marketing": is_subscribed = true }

Storing extra addresses as metafields

The staging model takes one address. To preserve the full address book on the source side:

{ "original_id": entity_id, "email": email, "firstName": firstname, "lastName": lastname, "address1": default_billing.street, "city": default_billing.city, "country": default_billing.country_id, "zip": default_billing.postcode, "dict_metafields": { "additional_addresses": $count(addresses) > 1 ? addresses[address_id != default_billing.address_id] : null } }

The auto-detected metafield type for an array is json — perfect for an address book the merchant can post-process later.

Tagging by customer group

{ "original_id": entity_id, "email": email, "firstName": firstname, "lastName": lastname, "tags": [ "group-" & $lowercase(group.code), is_b2b ? "b2b" : "b2c" ] }

Skipping guest checkouts

If your source allows orders by non-registered customers, those shouldn’t migrate as customers:

{ "original_id": entity_id, "exclude": is_guest = true, "email": email, }

Gotchas

Marketing consent without timestamp is suspicious. The staging model only sets marketingState: "SUBSCRIBED" — it doesn’t carry a consentUpdatedAt. If audit trail matters for the merchant (GDPR, CASL), capture the original opt-in date in dict_metafields so it’s preserved.

  • Email uniqueness is enforced by Shopify. Two source customers with the same email collide. Either dedupe in your mapping or accept that the second load fails for that row.
  • Phone format. Shopify rejects malformed phones. Normalize to E.164 in JSONata.
  • Country/province codes. ISO codes work everywhere; full names work in most cases but break for unusual spellings. Default to codes.
  • One address only in the staging model. If the merchant needs multi-address customers preserved exactly, store the extras as JSON metafields and post-process after migration.
  • accepts_email_marketing: true without email silently produces no consent (the staging model checks both). Same for SMS + phone.
Last updated on