{"id":3189,"date":"2026-10-04T00:06:41","date_gmt":"2026-10-04T00:06:41","guid":{"rendered":"https:\/\/dmarketertayeeb.com\/blog\/shopify-sms-consent-api-migration\/"},"modified":"2026-10-04T00:24:00","modified_gmt":"2026-10-04T00:24:00","slug":"shopify-sms-consent-api-migration","status":"publish","type":"post","link":"https:\/\/dmarketertayeeb.com\/blog\/shopify-sms-consent-api-migration\/","title":{"rendered":"Shopify SMS Consent API Migration: Update Phone Number Reads"},"content":{"rendered":"\n<p><strong>Short answer:<\/strong> Shopify&#8217;s October 1, 2026 changelog adds <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">CustomerPhoneNumber.smsMarketingConsent<\/code> in API version <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">2026-10<\/code>. Move Admin GraphQL reads to <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">customer.defaultPhoneNumber.smsMarketingConsent<\/code> and Customer Account API reads to <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">customer.phoneNumber.smsMarketingConsent.state<\/code>. Keep writes on the Admin GraphQL <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">customerSmsMarketingConsentUpdate<\/code> mutation: its input still uses <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">marketingState<\/code>, a different enum from the new read field&#8217;s <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">state<\/code>.<\/p>\n\n\n\n<p>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&#8217;s availability date, not a published removal deadline. <a href=\"https:\/\/shopify.dev\/changelog\/posts\/sms-marketing-consent-now-available-on-the-customerphonenumber-object\">Shopify&#8217;s changelog entry<\/a> names both the Admin GraphQL API and the Customer Account API.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Where the new field lives<\/h2>\n\n\n\n<p>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.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Admin GraphQL API<\/h3>\n\n\n\n<p>Start from <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">Customer.defaultPhoneNumber<\/code>, then select <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">smsMarketingConsent<\/code>. The current object includes <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">state<\/code>, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">optInLevel<\/code>, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">collectedFrom<\/code>, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">sourceLocation<\/code> and <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">updatedAt<\/code>. The old sibling fields <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">marketingState<\/code>, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">marketingOptInLevel<\/code>, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">marketingCollectedFrom<\/code>, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">marketingUpdatedAt<\/code> and <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">sourceLocation<\/code> are deprecated on <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">CustomerPhoneNumber<\/code>. Shopify lists those flat paths on the <a href=\"https:\/\/shopify.dev\/docs\/api\/admin-graphql\/2026-10\/objects\/CustomerPhoneNumber\">Admin CustomerPhoneNumber reference<\/a> and the nested fields on the <a href=\"https:\/\/shopify.dev\/docs\/api\/admin-graphql\/2026-10\/objects\/CustomerSmsMarketingConsent\">CustomerSmsMarketingConsent reference<\/a>. The phone-number reference requires <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">read_customers<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\" tabindex=\"0\" aria-label=\"Unexecuted Shopify SMS consent code example\" style=\"overflow-x:auto;max-width:100%;font-family:ui-monospace,SFMono-Regular,Consolas,monospace!important;font-size:14px;line-height:1.6;\"><code style=\"font-family:ui-monospace,SFMono-Regular,Consolas,monospace!important;font-size:inherit;line-height:inherit;white-space:pre;\">query CustomerSmsConsent($id: ID!) {\n  customer(id: $id) {\n    id\n    defaultPhoneNumber {\n      phoneNumber\n      smsMarketingConsent {\n        state\n        optInLevel\n        collectedFrom\n        updatedAt\n      }\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<p>In this Admin query, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">$id<\/code> is a variable for an existing Shopify Customer GID; the illustrative example contains no real customer identifier. Shopify&#8217;s <a href=\"https:\/\/shopify.dev\/docs\/api\/admin-graphql\/2026-10\/objects\/Customer\">Admin Customer reference<\/a> marks <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">defaultPhoneNumber<\/code> nullable, so handle the case where a customer has no default phone number. This minimal selection leaves out <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">sourceLocation<\/code> because that field returns a <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">Location<\/code> object. To request <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">sourceLocation { id name }<\/code>, the app also needs one of <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">read_locations<\/code>, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">read_inventory<\/code> or <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">read_markets_home<\/code>; <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">read_customers<\/code> alone does not cover that expansion. See Shopify&#8217;s <a href=\"https:\/\/shopify.dev\/docs\/api\/admin-graphql\/2026-10\/objects\/Location\">Location reference<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Customer Account API<\/h3>\n\n\n\n<p>The Customer Account API starts from <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">Customer.phoneNumber<\/code>. Its structured <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">smsMarketingConsent<\/code> object exposes only <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">state<\/code>; asking for Admin-only fields such as <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">optInLevel<\/code> or <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">updatedAt<\/code> does not match this surface&#8217;s schema. The <a href=\"https:\/\/shopify.dev\/docs\/api\/customer\/2026-10\/objects\/CustomerPhoneNumber\">Customer Account CustomerPhoneNumber reference<\/a> documents that object and requires <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">customer_read_customers<\/code>; apps using the API must also meet Shopify&#8217;s <a href=\"https:\/\/shopify.dev\/docs\/apps\/launch\/protected-customer-data\">protected customer data requirements<\/a>.<\/p>\n\n\n\n<pre class=\"wp-block-code\" tabindex=\"0\" aria-label=\"Unexecuted Shopify SMS consent code example\" style=\"overflow-x:auto;max-width:100%;font-family:ui-monospace,SFMono-Regular,Consolas,monospace!important;font-size:14px;line-height:1.6;\"><code style=\"font-family:ui-monospace,SFMono-Regular,Consolas,monospace!important;font-size:inherit;line-height:inherit;white-space:pre;\">query AccountSmsConsent {\n  customer {\n    phoneNumber {\n      phoneNumber\n      smsMarketingConsent {\n        state\n      }\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<p>The argument-free Customer Account <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">customer<\/code> query returns the currently authenticated buyer&#8217;s record. The <a href=\"https:\/\/shopify.dev\/docs\/api\/customer\/2026-10\/objects\/Customer\">Customer reference<\/a> marks <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">Customer.phoneNumber<\/code> 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 <a href=\"https:\/\/shopify.dev\/docs\/apps\/launch\/protected-customer-data\">protected-data requirements<\/a>.<\/p>\n\n\n<p>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 <a href=\"https:\/\/shopify.dev\/docs\/api\/customer\/2026-10#authentication\">Customer Account authentication reference<\/a> and its <a href=\"https:\/\/shopify.dev\/docs\/api\/usage\/authentication\">API authentication overview<\/a>. These source-checked examples have not been executed; the article includes no real customer ID or credentials.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Replace read paths, not just field names<\/h2>\n\n\n\n<p>For Admin GraphQL, map the deprecated fields on <a href=\"https:\/\/shopify.dev\/docs\/api\/admin-graphql\/2026-10\/objects\/CustomerPhoneNumber\"><code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">CustomerPhoneNumber<\/code><\/a> to the nested fields on <a href=\"https:\/\/shopify.dev\/docs\/api\/admin-graphql\/2026-10\/objects\/CustomerSmsMarketingConsent\"><code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">CustomerSmsMarketingConsent<\/code><\/a> as follows:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">marketingState<\/code> becomes <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">smsMarketingConsent.state<\/code>.<\/li><li><code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">marketingOptInLevel<\/code> becomes <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">smsMarketingConsent.optInLevel<\/code>.<\/li><li><code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">marketingCollectedFrom<\/code> becomes <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">smsMarketingConsent.collectedFrom<\/code>.<\/li><li><code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">marketingUpdatedAt<\/code> becomes <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">smsMarketingConsent.updatedAt<\/code>.<\/li><li><code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">sourceLocation<\/code> moves under <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">smsMarketingConsent<\/code>; request it only when the app has a Location read scope.<\/li><\/ul>\n\n\n\n<p>On the Customer Account API, the <a href=\"https:\/\/shopify.dev\/docs\/api\/customer\/2026-10\/objects\/CustomerPhoneNumber\">CustomerPhoneNumber reference<\/a> documents <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">marketingState<\/code> as deprecated; replace it with <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">smsMarketingConsent.state<\/code>. Do not request Admin consent metadata from that API. Also distinguish the deprecated Admin <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">Customer.smsMarketingConsent<\/code> field, whose type is <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">CustomerSmsMarketingConsentState<\/code>, from the new phone-level object. The Admin <a href=\"https:\/\/shopify.dev\/docs\/api\/admin-graphql\/2026-10\/objects\/Customer\">Customer reference<\/a> documents both that deprecated field and the new <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">Customer.defaultPhoneNumber<\/code> path.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Handle the state enum change explicitly<\/h2>\n\n\n\n<p>The new structured read uses <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">CustomerMarketingConsentState<\/code> in Admin GraphQL and <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">MarketingConsentState<\/code> in the Customer Account API. Shopify&#8217;s <a href=\"https:\/\/shopify.dev\/docs\/api\/admin-graphql\/2026-10\/enums\/CustomerMarketingConsentState\">Admin enum<\/a> and <a href=\"https:\/\/shopify.dev\/docs\/api\/customer\/2026-10\/enums\/MarketingConsentState\">Customer Account enum<\/a> list <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">NEVER_SUBSCRIBED<\/code>, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">PENDING<\/code>, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">REDACTED<\/code>, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">SUBSCRIBED<\/code> and <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">UNSUBSCRIBED<\/code>. The old Admin phone-level field and the Admin writer input use <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">CustomerSmsMarketingState<\/code>, whose first value is <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">NOT_SUBSCRIBED<\/code>, not <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">NEVER_SUBSCRIBED<\/code>; Shopify documents that older enum <a href=\"https:\/\/shopify.dev\/docs\/api\/admin-graphql\/2026-10\/enums\/CustomerSmsMarketingState\">here<\/a>.<\/p>\n\n\n\n<p>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 <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">NEVER_SUBSCRIBED<\/code> as read-only and <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">REDACTED<\/code> as internally set and read-only; do not send either as an update value.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Keep Admin consent writes on the dedicated mutation<\/h2>\n\n\n\n<p>The Admin GraphQL writer remains <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">customerSmsMarketingConsentUpdate<\/code>, which requires <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">write_customers<\/code>. Its input type is separate from the new structured read type: <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">CustomerSmsMarketingConsentInput<\/code> accepts <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">marketingState<\/code> (<code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">CustomerSmsMarketingState!<\/code>), <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">marketingOptInLevel<\/code>, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">consentUpdatedAt<\/code> and <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">sourceLocationId<\/code>. It does not accept <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">state<\/code>, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">collectedFrom<\/code> or <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">consentCollectedFrom<\/code>. Check the <a href=\"https:\/\/shopify.dev\/docs\/api\/admin-graphql\/2026-10\/mutations\/customerSmsMarketingConsentUpdate\">mutation reference<\/a> and its <a href=\"https:\/\/shopify.dev\/docs\/api\/admin-graphql\/2026-10\/input-objects\/CustomerSmsMarketingConsentInput\">input type<\/a> before regenerating a client.<\/p>\n\n\n\n<pre class=\"wp-block-code\" tabindex=\"0\" aria-label=\"Unexecuted Shopify SMS consent code example\" style=\"overflow-x:auto;max-width:100%;font-family:ui-monospace,SFMono-Regular,Consolas,monospace!important;font-size:14px;line-height:1.6;\"><code style=\"font-family:ui-monospace,SFMono-Regular,Consolas,monospace!important;font-size:inherit;line-height:inherit;white-space:pre;\">mutation UpdateCustomerSmsConsent($input: CustomerSmsMarketingConsentUpdateInput!) {\n  customerSmsMarketingConsentUpdate(input: $input) {\n    userErrors {\n      field\n      message\n    }\n    customer {\n      id\n      defaultPhoneNumber {\n        phoneNumber\n        smsMarketingConsent {\n          state\n          optInLevel\n          collectedFrom\n          updatedAt\n        }\n      }\n    }\n  }\n}<\/code><\/pre>\n\n\n\n<p>In this app-specific example, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">sourceRecord.smsConsentEvidence<\/code> 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.<\/p>\n\n\n\n<pre class=\"wp-block-code\" tabindex=\"0\" aria-label=\"Unexecuted Shopify SMS consent code example\" style=\"overflow-x:auto;max-width:100%;font-family:ui-monospace,SFMono-Regular,Consolas,monospace!important;font-size:14px;line-height:1.6;\"><code style=\"font-family:ui-monospace,SFMono-Regular,Consolas,monospace!important;font-size:inherit;line-height:inherit;white-space:pre;\">const smsState = mapVerifiedSmsEvidenceToAdminState(\n  sourceRecord.smsConsentEvidence\n);\nconst writableSmsStates = new Set([\n  \"NOT_SUBSCRIBED\",\n  \"PENDING\",\n  \"SUBSCRIBED\",\n  \"UNSUBSCRIBED\"\n]);\nif (!writableSmsStates.has(smsState)) {\n  throw new Error(\"No verified writable SMS consent state; do not write.\");\n}\n\nconst variables = {\n  input: {\n    customerId,\n    smsMarketingConsent: {\n      \/\/ Map verified source evidence to CustomerSmsMarketingState before this call.\n      marketingState: smsState,\n      ...(sourceRecord.optInLevel &amp;&amp; {\n        marketingOptInLevel: sourceRecord.optInLevel\n      }),\n      ...(sourceRecord.consentedAt &amp;&amp; {\n        consentUpdatedAt: sourceRecord.consentedAt\n      }),\n      ...(sourceRecord.locationId &amp;&amp; {\n        sourceLocationId: sourceRecord.locationId\n      })\n    }\n  }\n};<\/code><\/pre>\n\n\n\n<p>This is an illustrative, unexecuted request shape. The <a href=\"https:\/\/shopify.dev\/docs\/api\/admin-graphql\/2026-10\/input-objects\/CustomerSmsMarketingConsentUpdateInput\">update input reference<\/a> requires a unique phone number on the customer record; Shopify says to add the number with the Admin <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">customerUpdate<\/code> mutation first if it is missing. <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">marketingOptInLevel<\/code> and <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">sourceLocationId<\/code> are optional. The nested <a href=\"https:\/\/shopify.dev\/docs\/api\/admin-graphql\/2026-10\/input-objects\/CustomerSmsMarketingConsentInput\">consent input reference<\/a> defines <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">consentUpdatedAt<\/code> 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 <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">userErrors<\/code> on every response. The mutation documentation&#8217;s sample still selects the deprecated Customer-level consent object; select <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">defaultPhoneNumber.smsMarketingConsent<\/code> when you need the new structured readback.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Check permissions before the rollout<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>Admin GraphQL read: request <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">read_customers<\/code>. Admin write: request <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">write_customers<\/code>.<\/li><li>Customer Account API: the <a href=\"https:\/\/shopify.dev\/docs\/api\/customer\/2026-10\/objects\/CustomerPhoneNumber\">CustomerPhoneNumber reference<\/a> requires <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">customer_read_customers<\/code>; use the buyer-authenticated token described in Shopify\u2019s <a href=\"https:\/\/shopify.dev\/docs\/api\/customer\/2026-10#authentication\">Customer Account authentication reference<\/a>. The top-level <a href=\"https:\/\/shopify.dev\/docs\/api\/customer\/2026-10\/queries\/customer\"><code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">customer<\/code> query reference<\/a> separately lists <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">customer_read_payment_instrument_authenticated<\/code>. Shopify\u2019s <a href=\"https:\/\/shopify.dev\/docs\/api\/usage\/access-scopes\">access-scope guide<\/a> says some reference labels describe authentication states rather than requestable scopes, but doesn\u2019t map this particular label or its relation to <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">customer_read_customers<\/code>. Don\u2019t infer a payment permission or a minimum scope set from the query label alone.<\/li><li>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\u2019s <a href=\"https:\/\/shopify.dev\/docs\/apps\/launch\/protected-customer-data\">protected customer data requirements<\/a>.<\/li><li>If protected data is not approved, Shopify can redact fields and return GraphQL errors, as shown in its <a href=\"https:\/\/shopify.dev\/docs\/apps\/launch\/protected-customer-data\">protected-data examples<\/a>. Handle a redaction or missing phone separately from a consent state, not as <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">NEVER_SUBSCRIBED<\/code>.<\/li><\/ul>\n\n\n\n<p>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 <a href=\"https:\/\/shopify.dev\/docs\/api\/customer\/2026-10\/objects\/Mutation\">Mutation root<\/a> lists a WhatsApp consent update, not an SMS one, and <a href=\"https:\/\/shopify.dev\/docs\/api\/customer\/2026-10\/input-objects\/CustomerUpdateInput\">CustomerUpdateInput<\/a> contains only <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">firstName<\/code> and <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">lastName<\/code>. If your integration must write SMS consent, use an authorized Admin GraphQL path rather than guessing a Customer Account mutation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Keep phone data separate from marketing consent<\/h2>\n\n\n\n<p>A phone number is contact data; it does not prove SMS marketing opt-in. Shopify&#8217;s <a href=\"https:\/\/help.shopify.com\/en\/manual\/promoting-marketing\/create-marketing\/shopify-messaging\/sms\/deliverability\">SMS guidance<\/a> 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.<\/p>\n\n\n\n<p>For a staged migration, pin the app to API version <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">2026-10<\/code>, regenerate its GraphQL schema, update one read path at a time, and test state normalization with fixtures for <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">NEVER_SUBSCRIBED<\/code>, <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">REDACTED<\/code> and ordinary subscription changes. Keep the writer input separate, review every <code style=\"overflow-wrap:anywhere;word-break:normal;white-space:normal;\">userErrors<\/code> result, and validate the flow in a development store before changing production integrations.<\/p>\n\n\n\n<p>If the same app also handles event delivery, see the separate guide to <a href=\"https:\/\/dmarketertayeeb.com\/blog\/shopify-next-gen-events-migration-webhooks\/\">event subscription migration for Shopify apps<\/a>. For checkout scripts and measurement, see the guide to <a href=\"https:\/\/dmarketertayeeb.com\/blog\/shopify-script-tag-deprecation-migration-2026\/\">Shopify ScriptTag measurement changes<\/a>.<\/p>\n\n\n\n<p><strong>Disclosure:<\/strong> 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.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Shopify&#8217;s 2026-10 API adds structured SMS consent reads. See Admin and Customer Account paths, enum changes, access rules, and separate Admin mutation inputs.<\/p>\n","protected":false},"author":1,"featured_media":3188,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[177],"tags":[455,449],"class_list":["post-3189","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-digital-marketing","tag-api-migration","tag-shopify","has-featured-image"],"_links":{"self":[{"href":"https:\/\/dmarketertayeeb.com\/blog\/wp-json\/wp\/v2\/posts\/3189","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dmarketertayeeb.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dmarketertayeeb.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dmarketertayeeb.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dmarketertayeeb.com\/blog\/wp-json\/wp\/v2\/comments?post=3189"}],"version-history":[{"count":2,"href":"https:\/\/dmarketertayeeb.com\/blog\/wp-json\/wp\/v2\/posts\/3189\/revisions"}],"predecessor-version":[{"id":3193,"href":"https:\/\/dmarketertayeeb.com\/blog\/wp-json\/wp\/v2\/posts\/3189\/revisions\/3193"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dmarketertayeeb.com\/blog\/wp-json\/wp\/v2\/media\/3188"}],"wp:attachment":[{"href":"https:\/\/dmarketertayeeb.com\/blog\/wp-json\/wp\/v2\/media?parent=3189"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dmarketertayeeb.com\/blog\/wp-json\/wp\/v2\/categories?post=3189"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dmarketertayeeb.com\/blog\/wp-json\/wp\/v2\/tags?post=3189"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}