From 2793541df68234dc56b2d0e12cea4079f75acd79 Mon Sep 17 00:00:00 2001 From: andrewmcgov Date: Mon, 15 Dec 2025 16:45:46 -0500 Subject: [PATCH 1/5] Add intents to customer account extension api --- .../reference/apis/intents.doc.ts | 33 ++++ .../examples/apis/intents.example.jsx | 35 ++++ .../surfaces/customer-account/api/shared.ts | 182 ++++++++++++++++++ .../api/standard-api/standard-api.ts | 9 + 4 files changed, 259 insertions(+) create mode 100644 packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts create mode 100644 packages/ui-extensions/docs/surfaces/customer-account/reference/examples/apis/intents.example.jsx diff --git a/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts b/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts new file mode 100644 index 0000000000..6f3b288f6d --- /dev/null +++ b/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts @@ -0,0 +1,33 @@ +import type {ReferenceEntityTemplateSchema} from '@shopify/generate-docs'; + +const data: ReferenceEntityTemplateSchema = { + name: 'Intents', + overviewPreviewDescription: + 'The API for invoking Shopify intents to request workflows.', + description: `Entry point for Shopify intents. Intents pair an \`action\` (verb) with a resource \`type\` and optional \`value\` and \`data\` to request a workflow.`, + isVisualComponent: false, + category: 'APIs', + type: 'API', + definitions: [ + { + title: 'Intents', + description: 'Intents API for invoking Shopify workflows.', + type: 'Intents', + }, + ], + defaultExample: { + description: '', + codeblock: { + title: 'Extension.jsx', + tabs: [ + { + code: '../examples/apis/intents.example.jsx', + language: 'jsx', + }, + ], + }, + }, + related: [], +}; + +export default data; diff --git a/packages/ui-extensions/docs/surfaces/customer-account/reference/examples/apis/intents.example.jsx b/packages/ui-extensions/docs/surfaces/customer-account/reference/examples/apis/intents.example.jsx new file mode 100644 index 0000000000..2acb091709 --- /dev/null +++ b/packages/ui-extensions/docs/surfaces/customer-account/reference/examples/apis/intents.example.jsx @@ -0,0 +1,35 @@ +import '@shopify/ui-extensions/preact'; +import {render} from 'preact'; + +export default async () => { + render(, document.body); +}; + +function Extension() { + async function handleReplacePaymentMethod() { + const activity = await intents.invoke({ + action: 'open', + type: 'shopify/SubscriptionContract', + value: + 'gid://shopify/SubscriptionContract/123', + data: {field: 'paymentMethod'}, + }); + + const response = await activity.complete; + + if (response.code === 'ok') { + console.log( + 'Intent completed successfully', + response.data, + ); + } + } + + return ( + + Edit subscription payment method + + ); +} diff --git a/packages/ui-extensions/src/surfaces/customer-account/api/shared.ts b/packages/ui-extensions/src/surfaces/customer-account/api/shared.ts index f471918851..b17a4b1167 100644 --- a/packages/ui-extensions/src/surfaces/customer-account/api/shared.ts +++ b/packages/ui-extensions/src/surfaces/customer-account/api/shared.ts @@ -624,3 +624,185 @@ export interface ToastApiResult { export interface ToastApi { show: (content: string) => Promise; } + +/** + * Options for URL-based invocations. + * + * When invoking via URL syntax, `action` and `type` are parsed from the + * string. This companion type captures the remaining optional fields that can + * be provided alongside the URL. + */ +export interface IntentQueryOptions { + /** + * The resource identifier for edit actions (e.g., 'gid://shopify/SubscriptionContract/123'). + */ + value?: string; + /** + * Optional input payload passed to the intent. + * + * Used to seed forms or supply parameters. The accepted shape is + * intent-specific. For example: + * - Replacing a payment method on a subscription contract requires + * { field: 'paymentMethod' } + */ + data?: Record; +} + +/** + * Allowed actions that can be performed by an intent. + * + * Common actions include: + * - `'create'`: Initiate creation of a new resource. + * - `'open'`: Modify an existing resource. + */ +export type IntentAction = 'create' | 'open' | string; + +/** + * Structured description of an intent to invoke. + * + * Use this object form when programmatically composing an intent at runtime. + * It pairs an action (verb) with a resource type and optional inputs. + */ +export interface IntentQuery extends IntentQueryOptions { + /** + * Verb describing the operation to perform on the target resource. + * + * Common values include `'create'` and `'open'`. The set of + * allowed verbs is intent-specific; unknown verbs will fail validation. + */ + action: IntentAction; + /** + * The resource type (e.g., 'shopify/SubscriptionContract'). + */ + type: string; +} + +/** + * Successful intent completion. + * + * - `code` is always `'ok'` + * - `data` contains the output payload + */ +export interface SuccessIntentResponse { + code: 'ok'; + /** + * Validated output payload produced by the workflow. + * + * The shape is intent-specific. Consumers should narrow by `code === 'ok'` before accessing. + */ + data: Record; +} + +/** + * Failed intent completion. + * + * - `code` is always `'error'` + * - `message` summarizes the failure + * - `issues` optionally provides structured details for validation or + * field-specific problems following the Standard Schema convention + * + */ +export interface ErrorIntentResponse { + code?: 'error'; + message?: string; + issues?: { + /** + * The path to the field with the issue. + */ + path?: string[]; + /** + * The error message for the issue. + */ + message?: string; + }[]; +} + +/** + * User dismissed or closed the workflow without completing it. + * + * Distinct from `error`: no failure occurred, the activity was simply + * abandoned by the user. + */ +export interface ClosedIntentResponse { + code: 'closed'; +} + +/** + * Result of an intent activity. + * + * Discriminated union representing all possible completion outcomes for an + * invoked intent. + */ +export type IntentResponse = + | SuccessIntentResponse + | ErrorIntentResponse + | ClosedIntentResponse; + +/** + * Activity handle for tracking intent workflow progress. + */ +export interface IntentActivity { + /** + * A Promise that resolves when the intent workflow completes, returning the response. + */ + complete: Promise; +} + +/** + * Entry point for Shopify intents. + * + * Intents pair an `action` (verb) with a resource `type` and optional `value` + * and `data` to request a workflow. + */ +export interface Intents { + /** + * Invoke an intent using the object syntax. + * + * @param query - Structured intent description, including `action` and `type`. + * @returns A promise for an {@link IntentActivity} that completes with an + * {@link IntentResponse}. + * + * @example + * ```javascript + * const activity = await shopify.intents.invoke( + * { + * action: 'open', + * type: 'shopify/SubscriptionContract', + * value: 'gid://shopify/SubscriptionContract/69372608568', + * data: { field: 'paymentMethod' }, + * } + * ); + * ``` + */ + invoke(query: IntentQuery): Promise; + /** + * Invoke an intent using the URL syntax. + * + * URL format: `action:type[,value][?params]`. + * + * @param intentURL - Intent in URL form + * @param options - Optional supplemental inputs such as `value` or `data`. + * @returns A promise for an {@link IntentActivity} that completes with an + * {@link IntentResponse}. + * + * @example + * ```javascript + * // Using query string syntax + * const activity = await shopify.intents.invoke('open:shopify/SubscriptionContract,gid://shopify/SubscriptionContract/69372608568?field=paymentMethod'); + * + * // Or using a query string and options + * const activity = await shopify.intents.invoke( + * 'open:shopify/SubscriptionContract', + * { + * value: 'gid://shopify/SubscriptionContract/69372608568', + * data: { field: 'paymentMethod' }, + * } + * ); + * const response = await activity.complete; + * ``` + */ + invoke( + intentURL: string, + options?: IntentQueryOptions, + ): Promise; +} diff --git a/packages/ui-extensions/src/surfaces/customer-account/api/standard-api/standard-api.ts b/packages/ui-extensions/src/surfaces/customer-account/api/standard-api/standard-api.ts index 954502594b..5f0fe686cb 100644 --- a/packages/ui-extensions/src/surfaces/customer-account/api/standard-api/standard-api.ts +++ b/packages/ui-extensions/src/surfaces/customer-account/api/standard-api/standard-api.ts @@ -12,6 +12,7 @@ import { CustomerPrivacy, ApplyTrackingConsentChangeType, ToastApi, + Intents, SubscribableSignalLike, } from '../shared'; @@ -87,6 +88,14 @@ export interface StandardApi { */ analytics: Analytics; + /** + * Entry point for Shopify intents. + * + * Intents pair an `action` (verb) with a resource `type` and optional `value` + * and `data` to request a workflow. + */ + intents: Intents; + /** * The settings matching the settings definition written in the * [`shopify.ui.extension.toml`](https://shopify.dev/docs/api/customer-account-ui-extensions/configuration) file. From 4a8c631aae7ee90d421ea6cd16380abb6176f16b Mon Sep 17 00:00:00 2001 From: Graeme Kemp Date: Thu, 18 Dec 2025 11:40:55 -0500 Subject: [PATCH 2/5] Expand Intents API documentation with invoke, types, and examples --- .../reference/apis/intents.doc.ts | 85 +++++++++++++++++-- 1 file changed, 76 insertions(+), 9 deletions(-) diff --git a/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts b/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts index 6f3b288f6d..a05ef84829 100644 --- a/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts +++ b/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts @@ -4,21 +4,14 @@ const data: ReferenceEntityTemplateSchema = { name: 'Intents', overviewPreviewDescription: 'The API for invoking Shopify intents to request workflows.', - description: `Entry point for Shopify intents. Intents pair an \`action\` (verb) with a resource \`type\` and optional \`value\` and \`data\` to request a workflow.`, + description: `The Intents API provides a way to invoke existing customer account workflows for managing buyer information.`, isVisualComponent: false, category: 'APIs', type: 'API', - definitions: [ - { - title: 'Intents', - description: 'Intents API for invoking Shopify workflows.', - type: 'Intents', - }, - ], defaultExample: { description: '', codeblock: { - title: 'Extension.jsx', + title: 'Replace payment method', tabs: [ { code: '../examples/apis/intents.example.jsx', @@ -27,6 +20,80 @@ const data: ReferenceEntityTemplateSchema = { ], }, }, + definitions: [ + { + title: 'invoke', + description: `The \`invoke\` API is a function that accepts either a string query or an options object describing the intent to invoke and returns a Promise that resolves to an activity handle for the workflow. + +## Intent Format + +Intents are invoked using a string query format: \`\${action}:\${type},\${value}\` + +Where: +- \`action\` - The operation to perform (\`create\`, \`edit\` or \`open\`) +- \`type\` - The resource type (e.g., \`shopify/SubscriptionContract\`) +- \`value\` - The resource identifier + +## Supported Resources + +### Subscription Contract +| Action | Type | Value | Data | +|--------|------|-------|------| +| \`open\` | \`shopify/SubscriptionContract\` | \`gid://shopify/SubscriptionContract/{id}\` | \`{ field: 'paymentMethod' }\` |`, + type: 'Intents', + }, + { + title: 'IntentAction', + description: `Supported actions that can be performed on resources. +- \`create\`: Opens a creation workflow for a new resource +- \`edit\`: Opens an editing workflow for an existing resource (requires \`value\` parameter) +- \`open\`: Opens a workflow for an existing resource (requires \`value\` parameter)`, + type: 'IntentAction', + }, + { + title: 'IntentQuery', + description: `Structured description of an intent to invoke. Use this object form when programmatically composing an intent at runtime.`, + type: 'IntentQuery', + }, + { + title: 'IntentQueryOptions', + description: `Options for invoking intents when using the query string format.`, + type: 'IntentQueryOptions', + }, + { + title: 'IntentActivity', + description: `Activity handle for tracking intent workflow progress.`, + type: 'IntentActivity', + }, + { + title: 'IntentResponse', + description: `Response object returned when the intent workflow completes.`, + type: 'IntentResponse', + }, + ], + examples: { + description: 'Intents for each Shopify resource type', + exampleGroups: [ + { + title: 'Subscription Contract', + examples: [ + { + description: + 'Replace the payment method on an active subscription contract. Opens a modal with vaulted cards.', + codeblock: { + title: 'Replace payment method', + tabs: [ + { + code: '../examples/apis/intents.example.jsx', + language: 'jsx', + }, + ], + }, + }, + ], + }, + ], + }, related: [], }; From 26de7519932a855a1e57adc28ade0c9bdacfdad0 Mon Sep 17 00:00:00 2001 From: andrewmcgov Date: Thu, 15 Jan 2026 11:30:08 -0500 Subject: [PATCH 3/5] Add string syntax example to intents docs --- .../reference/apis/intents.doc.ts | 30 +++++++----------- .../examples/apis/intents.example.jsx | 16 +++++----- .../examples/apis/intents.string.example.jsx | 31 +++++++++++++++++++ 3 files changed, 52 insertions(+), 25 deletions(-) create mode 100644 packages/ui-extensions/docs/surfaces/customer-account/reference/examples/apis/intents.string.example.jsx diff --git a/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts b/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts index a05ef84829..7495776001 100644 --- a/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts +++ b/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts @@ -11,7 +11,7 @@ const data: ReferenceEntityTemplateSchema = { defaultExample: { description: '', codeblock: { - title: 'Replace payment method', + title: 'Replace payment method with object syntax', tabs: [ { code: '../examples/apis/intents.example.jsx', @@ -46,7 +46,6 @@ Where: title: 'IntentAction', description: `Supported actions that can be performed on resources. - \`create\`: Opens a creation workflow for a new resource -- \`edit\`: Opens an editing workflow for an existing resource (requires \`value\` parameter) - \`open\`: Opens a workflow for an existing resource (requires \`value\` parameter)`, type: 'IntentAction', }, @@ -73,24 +72,19 @@ Where: ], examples: { description: 'Intents for each Shopify resource type', - exampleGroups: [ + examples: [ { - title: 'Subscription Contract', - examples: [ - { - description: - 'Replace the payment method on an active subscription contract. Opens a modal with vaulted cards.', - codeblock: { - title: 'Replace payment method', - tabs: [ - { - code: '../examples/apis/intents.example.jsx', - language: 'jsx', - }, - ], + description: + 'Replace the payment method on an active subscription contract. Opens a modal with vaulted cards.', + codeblock: { + title: 'Replace payment method with string syntax', + tabs: [ + { + code: '../examples/apis/intents.string.example.jsx', + language: 'jsx', }, - }, - ], + ], + }, }, ], }, diff --git a/packages/ui-extensions/docs/surfaces/customer-account/reference/examples/apis/intents.example.jsx b/packages/ui-extensions/docs/surfaces/customer-account/reference/examples/apis/intents.example.jsx index 2acb091709..fa38b3c94f 100644 --- a/packages/ui-extensions/docs/surfaces/customer-account/reference/examples/apis/intents.example.jsx +++ b/packages/ui-extensions/docs/surfaces/customer-account/reference/examples/apis/intents.example.jsx @@ -7,13 +7,15 @@ export default async () => { function Extension() { async function handleReplacePaymentMethod() { - const activity = await intents.invoke({ - action: 'open', - type: 'shopify/SubscriptionContract', - value: - 'gid://shopify/SubscriptionContract/123', - data: {field: 'paymentMethod'}, - }); + const activity = await shopify.intents.invoke( + { + action: 'open', + type: 'shopify/SubscriptionContract', + value: + 'gid://shopify/SubscriptionContract/123', + data: {field: 'paymentMethod'}, + }, + ); const response = await activity.complete; diff --git a/packages/ui-extensions/docs/surfaces/customer-account/reference/examples/apis/intents.string.example.jsx b/packages/ui-extensions/docs/surfaces/customer-account/reference/examples/apis/intents.string.example.jsx new file mode 100644 index 0000000000..10c835391a --- /dev/null +++ b/packages/ui-extensions/docs/surfaces/customer-account/reference/examples/apis/intents.string.example.jsx @@ -0,0 +1,31 @@ +import '@shopify/ui-extensions/preact'; +import {render} from 'preact'; + +export default async () => { + render(, document.body); +}; + +function Extension() { + async function handleReplacePaymentMethod() { + const activity = await shopify.intents.invoke( + 'open:shopify/SubscriptionContract,gid://shopify/SubscriptionContract/123?field=paymentMethod', + ); + + const response = await activity.complete; + + if (response.code === 'ok') { + console.log( + 'Intent completed successfully', + response.data, + ); + } + } + + return ( + + Edit subscription payment method + + ); +} From 4c03959921c964c01d764b4cac1e11387511251e Mon Sep 17 00:00:00 2001 From: Graeme Kemp Date: Fri, 16 Jan 2026 14:35:38 -0500 Subject: [PATCH 4/5] Update Intents API descriptions --- .../src/surfaces/customer-account/api/shared.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/ui-extensions/src/surfaces/customer-account/api/shared.ts b/packages/ui-extensions/src/surfaces/customer-account/api/shared.ts index b17a4b1167..f0c5bedf8a 100644 --- a/packages/ui-extensions/src/surfaces/customer-account/api/shared.ts +++ b/packages/ui-extensions/src/surfaces/customer-account/api/shared.ts @@ -634,7 +634,7 @@ export interface ToastApi { */ export interface IntentQueryOptions { /** - * The resource identifier for edit actions (e.g., 'gid://shopify/SubscriptionContract/123'). + * The resource identifier for edit actions (e.g. `gid://shopify/SubscriptionContract/123`). */ value?: string; /** @@ -667,12 +667,12 @@ export interface IntentQuery extends IntentQueryOptions { /** * Verb describing the operation to perform on the target resource. * - * Common values include `'create'` and `'open'`. The set of + * Common values include `create` and `open`. The set of * allowed verbs is intent-specific; unknown verbs will fail validation. */ action: IntentAction; /** - * The resource type (e.g., 'shopify/SubscriptionContract'). + * The resource type (e.g. `shopify/SubscriptionContract`). */ type: string; } @@ -756,7 +756,9 @@ export interface IntentActivity { */ export interface Intents { /** - * Invoke an intent using the object syntax. + * Invoke an intent using the object or URL syntax. + * + * Object format: `{action, type, value?, data?}` * * @param query - Structured intent description, including `action` and `type`. * @returns A promise for an {@link IntentActivity} that completes with an @@ -776,9 +778,7 @@ export interface Intents { */ invoke(query: IntentQuery): Promise; /** - * Invoke an intent using the URL syntax. - * - * URL format: `action:type[,value][?params]`. + * URL format: `action:type[,value][?params]` * * @param intentURL - Intent in URL form * @param options - Optional supplemental inputs such as `value` or `data`. From c64abe46ec9036fff1147afe69c30363252a1d76 Mon Sep 17 00:00:00 2001 From: Graeme Kemp Date: Fri, 16 Jan 2026 16:37:38 -0500 Subject: [PATCH 5/5] sentence case section titles --- .../surfaces/customer-account/reference/apis/intents.doc.ts | 6 +++--- .../src/surfaces/customer-account/api/shared.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts b/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts index 7495776001..9095dac3c2 100644 --- a/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts +++ b/packages/ui-extensions/docs/surfaces/customer-account/reference/apis/intents.doc.ts @@ -25,7 +25,7 @@ const data: ReferenceEntityTemplateSchema = { title: 'invoke', description: `The \`invoke\` API is a function that accepts either a string query or an options object describing the intent to invoke and returns a Promise that resolves to an activity handle for the workflow. -## Intent Format +## Intent format Intents are invoked using a string query format: \`\${action}:\${type},\${value}\` @@ -34,9 +34,9 @@ Where: - \`type\` - The resource type (e.g., \`shopify/SubscriptionContract\`) - \`value\` - The resource identifier -## Supported Resources +## Supported resources -### Subscription Contract +### Subscription contract | Action | Type | Value | Data | |--------|------|-------|------| | \`open\` | \`shopify/SubscriptionContract\` | \`gid://shopify/SubscriptionContract/{id}\` | \`{ field: 'paymentMethod' }\` |`, diff --git a/packages/ui-extensions/src/surfaces/customer-account/api/shared.ts b/packages/ui-extensions/src/surfaces/customer-account/api/shared.ts index f0c5bedf8a..cee343446d 100644 --- a/packages/ui-extensions/src/surfaces/customer-account/api/shared.ts +++ b/packages/ui-extensions/src/surfaces/customer-account/api/shared.ts @@ -757,7 +757,7 @@ export interface IntentActivity { export interface Intents { /** * Invoke an intent using the object or URL syntax. - * + * * Object format: `{action, type, value?, data?}` * * @param query - Structured intent description, including `action` and `type`.