Short answer: Shopify’s October 1, 2026 changelog adds CustomerPhoneNumber.smsMarketingConsent in API version 2026-10. Move Admin GraphQL reads to customer.defaultPhoneNumber.smsMarketingConsent and Customer Account API reads to customer.phoneNumber.smsMarketingConsent.state. Keep writes on the Admin GraphQL customerSmsMarketingConsentUpdate mutation: its input still uses marketingState, a different enum from the new read field’s state.
The announcement says the old flat SMS consent fields remain available for backward compatibility but are deprecated. It gives no shutdown date, so October 1 is the new field’s availability date, not a published removal deadline. Shopify’s changelog entry names both the Admin GraphQL API and the Customer Account API.
Where the new field lives
The objects differ slightly by API surface. Admin GraphQL exposes the full structured consent record. The Customer Account API exposes only its current state on the structured consent object.
Admin GraphQL API
Start from Customer.defaultPhoneNumber, then select smsMarketingConsent. The current object includes state, optInLevel, collectedFrom, sourceLocation and updatedAt. The old sibling fields marketingState, marketingOptInLevel, marketingCollectedFrom, marketingUpdatedAt and sourceLocation are deprecated on CustomerPhoneNumber. Shopify lists those flat paths on the Admin CustomerPhoneNumber reference and the nested fields on the CustomerSmsMarketingConsent reference. The phone-number reference requires read_customers.
query CustomerSmsConsent($id: ID!) {
customer(id: $id) {
id
defaultPhoneNumber {
phoneNumber
smsMarketingConsent {
state
optInLevel
collectedFrom
updatedAt
}
}
}
}
In this Admin query, $id is a variable for an existing Shopify Customer GID; the illustrative example contains no real customer identifier. Shopify’s Admin Customer reference marks defaultPhoneNumber nullable, so handle the case where a customer has no default phone number. This minimal selection leaves out sourceLocation because that field returns a Location object. To request sourceLocation { id name }, the app also needs one of read_locations, read_inventory or read_markets_home; read_customers alone does not cover that expansion. See Shopify’s Location reference.
Customer Account API
The Customer Account API starts from Customer.phoneNumber. Its structured smsMarketingConsent object exposes only state; asking for Admin-only fields such as optInLevel or updatedAt does not match this surface’s schema. The Customer Account CustomerPhoneNumber reference documents that object and requires customer_read_customers; apps using the API must also meet Shopify’s protected customer data requirements.
query AccountSmsConsent {
customer {
phoneNumber {
phoneNumber
smsMarketingConsent {
state
}
}
}
}
The argument-free Customer Account customer query returns the currently authenticated buyer’s record. The Customer reference marks Customer.phoneNumber nullable, so a buyer can have no phone record; separately, the nested phone string can be withheld when protected-data access is not approved. Shopify shows the redaction behavior in its protected-data requirements.
Customer Account requests use an access token associated with the buyer, while Admin GraphQL uses an app token acting for the merchant and the Customer GID above. Shopify explains this distinction in the Customer Account authentication reference and its API authentication overview. These source-checked examples have not been executed; the article includes no real customer ID or credentials.
Replace read paths, not just field names
For Admin GraphQL, map the deprecated fields on CustomerPhoneNumber to the nested fields on CustomerSmsMarketingConsent as follows:
marketingStatebecomessmsMarketingConsent.state.marketingOptInLevelbecomessmsMarketingConsent.optInLevel.marketingCollectedFrombecomessmsMarketingConsent.collectedFrom.marketingUpdatedAtbecomessmsMarketingConsent.updatedAt.sourceLocationmoves undersmsMarketingConsent; request it only when the app has a Location read scope.
On the Customer Account API, the CustomerPhoneNumber reference documents marketingState as deprecated; replace it with smsMarketingConsent.state. Do not request Admin consent metadata from that API. Also distinguish the deprecated Admin Customer.smsMarketingConsent field, whose type is CustomerSmsMarketingConsentState, from the new phone-level object. The Admin Customer reference documents both that deprecated field and the new Customer.defaultPhoneNumber path.
Handle the state enum change explicitly
The new structured read uses CustomerMarketingConsentState in Admin GraphQL and MarketingConsentState in the Customer Account API. Shopify’s Admin enum and Customer Account enum list NEVER_SUBSCRIBED, PENDING, REDACTED, SUBSCRIBED and UNSUBSCRIBED. The old Admin phone-level field and the Admin writer input use CustomerSmsMarketingState, whose first value is NOT_SUBSCRIBED, not NEVER_SUBSCRIBED; Shopify documents that older enum here.
Do not cast the new read enum into the old mutation enum or use a global string replacement. The references do not provide a direct mapping table. Define an explicit adapter in your app and cover each state in fixtures. Shopify marks NEVER_SUBSCRIBED as read-only and REDACTED as internally set and read-only; do not send either as an update value.
Keep Admin consent writes on the dedicated mutation
The Admin GraphQL writer remains customerSmsMarketingConsentUpdate, which requires write_customers. Its input type is separate from the new structured read type: CustomerSmsMarketingConsentInput accepts marketingState (CustomerSmsMarketingState!), marketingOptInLevel, consentUpdatedAt and sourceLocationId. It does not accept state, collectedFrom or consentCollectedFrom. Check the mutation reference and its input type before regenerating a client.
mutation UpdateCustomerSmsConsent($input: CustomerSmsMarketingConsentUpdateInput!) {
customerSmsMarketingConsentUpdate(input: $input) {
userErrors {
field
message
}
customer {
id
defaultPhoneNumber {
phoneNumber
smsMarketingConsent {
state
optInLevel
collectedFrom
updatedAt
}
}
}
}
}
In this app-specific example, sourceRecord.smsConsentEvidence represents a verified SMS-specific record. Map that evidence explicitly to the Admin writer enum; do not cast either structured-read enum, infer consent from a stored phone number, or supply a default state. The guard below rejects unknown and internally set read-only enum values before constructing a request.
const smsState = mapVerifiedSmsEvidenceToAdminState(
sourceRecord.smsConsentEvidence
);
const writableSmsStates = new Set([
"NOT_SUBSCRIBED",
"PENDING",
"SUBSCRIBED",
"UNSUBSCRIBED"
]);
if (!writableSmsStates.has(smsState)) {
throw new Error("No verified writable SMS consent state; do not write.");
}
const variables = {
input: {
customerId,
smsMarketingConsent: {
// Map verified source evidence to CustomerSmsMarketingState before this call.
marketingState: smsState,
...(sourceRecord.optInLevel && {
marketingOptInLevel: sourceRecord.optInLevel
}),
...(sourceRecord.consentedAt && {
consentUpdatedAt: sourceRecord.consentedAt
}),
...(sourceRecord.locationId && {
sourceLocationId: sourceRecord.locationId
})
}
}
};
This is an illustrative, unexecuted request shape. The update input reference requires a unique phone number on the customer record; Shopify says to add the number with the Admin customerUpdate mutation first if it is missing. marketingOptInLevel and sourceLocationId are optional. The nested consent input reference defines consentUpdatedAt as the time the customer consented; if omitted, Shopify uses when the consent information was sent. Preserve the original time when available and do not label the request-time fallback as historical consent. Inspect userErrors on every response. The mutation documentation’s sample still selects the deprecated Customer-level consent object; select defaultPhoneNumber.smsMarketingConsent when you need the new structured readback.
Check permissions before the rollout
- Admin GraphQL read: request
read_customers. Admin write: requestwrite_customers. - Customer Account API: the CustomerPhoneNumber reference requires
customer_read_customers; use the buyer-authenticated token described in Shopify’s Customer Account authentication reference. The top-levelcustomerquery reference separately listscustomer_read_payment_instrument_authenticated. Shopify’s access-scope guide says some reference labels describe authentication states rather than requestable scopes, but doesn’t map this particular label or its relation tocustomer_read_customers. Don’t infer a payment permission or a minimum scope set from the query label alone. - For public apps, Shopify classifies customer phone as Level 2 protected customer data and requires a request for protected customer data plus the phone field. Apps installed only on development stores can access the selected data and fields without submitting for review after completing that selection. A GraphQL scope alone does not establish approval; see Shopify’s protected customer data requirements.
- If protected data is not approved, Shopify can redact fields and return GraphQL errors, as shown in its protected-data examples. Handle a redaction or missing phone separately from a consent state, not as
NEVER_SUBSCRIBED.
The checked Customer Account API references expose the new state for reading but do not document an SMS consent update route. The current Customer Account Mutation root lists a WhatsApp consent update, not an SMS one, and CustomerUpdateInput contains only firstName and lastName. If your integration must write SMS consent, use an authorized Admin GraphQL path rather than guessing a Customer Account mutation.
Keep phone data separate from marketing consent
A phone number is contact data; it does not prove SMS marketing opt-in. Shopify’s SMS guidance says customers need to opt in to SMS specifically and that email marketing consent does not apply to SMS. Treat a null phone number, a missing permission error and a consent state as different cases. This API field records a channel consent state; it does not make a collection flow legally compliant or guarantee that a message can be sent.
For a staged migration, pin the app to API version 2026-10, regenerate its GraphQL schema, update one read path at a time, and test state normalization with fixtures for NEVER_SUBSCRIBED, REDACTED and ordinary subscription changes. Keep the writer input separate, review every userErrors result, and validate the flow in a development store before changing production integrations.
If the same app also handles event delivery, see the separate guide to event subscription migration for Shopify apps. For checkout scripts and measurement, see the guide to Shopify ScriptTag measurement changes.
Disclosure: This article was drafted with AI assistance. The schema details were checked against the linked Shopify references; the GraphQL examples were not run against a store.