Skip to content

Commit d241b0d

Browse files
committed
Add intents to customer account extension api
1 parent 092a4ca commit d241b0d

File tree

4 files changed

+259
-0
lines changed

4 files changed

+259
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type {ReferenceEntityTemplateSchema} from '@shopify/generate-docs';
2+
3+
const data: ReferenceEntityTemplateSchema = {
4+
name: 'Intents',
5+
overviewPreviewDescription:
6+
'The API for invoking Shopify intents to request workflows.',
7+
description: `Entry point for Shopify intents. Intents pair an \`action\` (verb) with a resource \`type\` and optional \`value\` and \`data\` to request a workflow.`,
8+
isVisualComponent: false,
9+
category: 'APIs',
10+
type: 'API',
11+
definitions: [
12+
{
13+
title: 'Intents',
14+
description: 'Intents API for invoking Shopify workflows.',
15+
type: 'Intents',
16+
},
17+
],
18+
defaultExample: {
19+
description: '',
20+
codeblock: {
21+
title: 'Extension.jsx',
22+
tabs: [
23+
{
24+
code: '../examples/apis/intents.example.jsx',
25+
language: 'jsx',
26+
},
27+
],
28+
},
29+
},
30+
related: [],
31+
};
32+
33+
export default data;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import '@shopify/ui-extensions/preact';
2+
import {render} from 'preact';
3+
4+
export default async () => {
5+
render(<Extension />, document.body);
6+
};
7+
8+
function Extension() {
9+
async function handleReplacePaymentMethod() {
10+
const activity = await intents.invoke({
11+
action: 'open',
12+
type: 'shopify/SubscriptionContract',
13+
value:
14+
'gid://shopify/SubscriptionContract/123',
15+
data: {field: 'paymentMethod'},
16+
});
17+
18+
const response = await activity.complete;
19+
20+
if (response.code === 'ok') {
21+
console.log(
22+
'Intent completed successfully',
23+
response.data,
24+
);
25+
}
26+
}
27+
28+
return (
29+
<s-button
30+
onClick={handleReplacePaymentMethod}
31+
>
32+
Edit subscription payment method
33+
</s-button>
34+
);
35+
}

packages/ui-extensions/src/surfaces/customer-account/api/shared.ts

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,3 +624,185 @@ export interface ToastApiResult {
624624
export interface ToastApi {
625625
show: (content: string) => Promise<ToastApiResult>;
626626
}
627+
628+
/**
629+
* Options for URL-based invocations.
630+
*
631+
* When invoking via URL syntax, `action` and `type` are parsed from the
632+
* string. This companion type captures the remaining optional fields that can
633+
* be provided alongside the URL.
634+
*/
635+
export interface IntentQueryOptions {
636+
/**
637+
* The resource identifier for edit actions (e.g., 'gid://shopify/SubscriptionContract/123').
638+
*/
639+
value?: string;
640+
/**
641+
* Optional input payload passed to the intent.
642+
*
643+
* Used to seed forms or supply parameters. The accepted shape is
644+
* intent-specific. For example:
645+
* - Replacing a payment method on a subscription contract requires
646+
* { field: 'paymentMethod' }
647+
*/
648+
data?: Record<string, unknown>;
649+
}
650+
651+
/**
652+
* Allowed actions that can be performed by an intent.
653+
*
654+
* Common actions include:
655+
* - `'create'`: Initiate creation of a new resource.
656+
* - `'open'`: Modify an existing resource.
657+
*/
658+
export type IntentAction = 'create' | 'open' | string;
659+
660+
/**
661+
* Structured description of an intent to invoke.
662+
*
663+
* Use this object form when programmatically composing an intent at runtime.
664+
* It pairs an action (verb) with a resource type and optional inputs.
665+
*/
666+
export interface IntentQuery extends IntentQueryOptions {
667+
/**
668+
* Verb describing the operation to perform on the target resource.
669+
*
670+
* Common values include `'create'` and `'open'`. The set of
671+
* allowed verbs is intent-specific; unknown verbs will fail validation.
672+
*/
673+
action: IntentAction;
674+
/**
675+
* The resource type (e.g., 'shopify/SubscriptionContract').
676+
*/
677+
type: string;
678+
}
679+
680+
/**
681+
* Successful intent completion.
682+
*
683+
* - `code` is always `'ok'`
684+
* - `data` contains the output payload
685+
*/
686+
export interface SuccessIntentResponse {
687+
code: 'ok';
688+
/**
689+
* Validated output payload produced by the workflow.
690+
*
691+
* The shape is intent-specific. Consumers should narrow by `code === 'ok'` before accessing.
692+
*/
693+
data: Record<string, unknown>;
694+
}
695+
696+
/**
697+
* Failed intent completion.
698+
*
699+
* - `code` is always `'error'`
700+
* - `message` summarizes the failure
701+
* - `issues` optionally provides structured details for validation or
702+
* field-specific problems following the Standard Schema convention
703+
*
704+
*/
705+
export interface ErrorIntentResponse {
706+
code?: 'error';
707+
message?: string;
708+
issues?: {
709+
/**
710+
* The path to the field with the issue.
711+
*/
712+
path?: string[];
713+
/**
714+
* The error message for the issue.
715+
*/
716+
message?: string;
717+
}[];
718+
}
719+
720+
/**
721+
* User dismissed or closed the workflow without completing it.
722+
*
723+
* Distinct from `error`: no failure occurred, the activity was simply
724+
* abandoned by the user.
725+
*/
726+
export interface ClosedIntentResponse {
727+
code: 'closed';
728+
}
729+
730+
/**
731+
* Result of an intent activity.
732+
*
733+
* Discriminated union representing all possible completion outcomes for an
734+
* invoked intent.
735+
*/
736+
export type IntentResponse =
737+
| SuccessIntentResponse
738+
| ErrorIntentResponse
739+
| ClosedIntentResponse;
740+
741+
/**
742+
* Activity handle for tracking intent workflow progress.
743+
*/
744+
export interface IntentActivity {
745+
/**
746+
* A Promise that resolves when the intent workflow completes, returning the response.
747+
*/
748+
complete: Promise<IntentResponse>;
749+
}
750+
751+
/**
752+
* Entry point for Shopify intents.
753+
*
754+
* Intents pair an `action` (verb) with a resource `type` and optional `value`
755+
* and `data` to request a workflow.
756+
*/
757+
export interface Intents {
758+
/**
759+
* Invoke an intent using the object syntax.
760+
*
761+
* @param query - Structured intent description, including `action` and `type`.
762+
* @returns A promise for an {@link IntentActivity} that completes with an
763+
* {@link IntentResponse}.
764+
*
765+
* @example
766+
* ```javascript
767+
* const activity = await shopify.intents.invoke(
768+
* {
769+
* action: 'open',
770+
* type: 'shopify/SubscriptionContract',
771+
* value: 'gid://shopify/SubscriptionContract/69372608568',
772+
* data: { field: 'paymentMethod' },
773+
* }
774+
* );
775+
* ```
776+
*/
777+
invoke(query: IntentQuery): Promise<IntentActivity>;
778+
/**
779+
* Invoke an intent using the URL syntax.
780+
*
781+
* URL format: `action:type[,value][?params]`.
782+
*
783+
* @param intentURL - Intent in URL form
784+
* @param options - Optional supplemental inputs such as `value` or `data`.
785+
* @returns A promise for an {@link IntentActivity} that completes with an
786+
* {@link IntentResponse}.
787+
*
788+
* @example
789+
* ```javascript
790+
* // Using query string syntax
791+
* const activity = await shopify.intents.invoke('open:shopify/SubscriptionContract,gid://shopify/SubscriptionContract/69372608568?field=paymentMethod');
792+
*
793+
* // Or using a query string and options
794+
* const activity = await shopify.intents.invoke(
795+
* 'open:shopify/SubscriptionContract',
796+
* {
797+
* value: 'gid://shopify/SubscriptionContract/69372608568',
798+
* data: { field: 'paymentMethod' },
799+
* }
800+
* );
801+
* const response = await activity.complete;
802+
* ```
803+
*/
804+
invoke(
805+
intentURL: string,
806+
options?: IntentQueryOptions,
807+
): Promise<IntentActivity>;
808+
}

packages/ui-extensions/src/surfaces/customer-account/api/standard-api/standard-api.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
CustomerPrivacy,
1313
ApplyTrackingConsentChangeType,
1414
ToastApi,
15+
Intents,
1516
SubscribableSignalLike,
1617
} from '../shared';
1718

@@ -87,6 +88,14 @@ export interface StandardApi<Target extends ExtensionTarget = ExtensionTarget> {
8788
*/
8889
analytics: Analytics;
8990

91+
/**
92+
* Entry point for Shopify intents.
93+
*
94+
* Intents pair an `action` (verb) with a resource `type` and optional `value`
95+
* and `data` to request a workflow.
96+
*/
97+
intents: Intents;
98+
9099
/**
91100
* The settings matching the settings definition written in the
92101
* [`shopify.ui.extension.toml`](https://shopify.dev/docs/api/customer-account-ui-extensions/configuration) file.

0 commit comments

Comments
 (0)