Skip to main content

Extension points

Deprecated

Product subscription app extensions won't be supported as of December 3, 2025. You should migrate existing product subscription app extensions to purchase options extensions.

You've generated a product subscription app extension, and you now have a project folder with the extension script (either ./index.ts(x) or ./index.js). This guide describes the extension points in the script and how the script renders data and UI components.


Each extension point is triggered by a different merchant action, receives different data, and is responsible for handling a distinct part of the subscription experience.

The product subscription app extension uses the following extension points:

Note

The extension points must be rendered separately.

Extension points
Extension pointModeDescription
Admin::Product::SubscriptionPlan::AddAddAdd an existing purchase option to a product or variant.
Admin::Product::SubscriptionPlan::CreateCreateCreate a new purchase option
Admin::Product::SubscriptionPlan::EditEditEdit an existing purchase option
Admin::Product::SubscriptionPlan::RemoveRemoveRemove an existing purchase option from a product or variant

The following example shows how to render the extension points in JavaScript and React:

src/index.js

import {extend} from '@shopify/admin-ui-extensions';

function Add(root, api) {
root.appendChild(root.createText('Hello, world'));
root.mount();
}
function Create() {
/* ... */
}
function Edit() {
/* ... */
}
function Remove() {
/* ... */
}

extend(
'Admin::Product::SubscriptionPlan::Add',
Add,
);
extend(
'Admin::Product::SubscriptionPlan::Create',
Create,
);
extend(
'Admin::Product::SubscriptionPlan::Edit',
Edit,
);
extend(
'Admin::Product::SubscriptionPlan::Remove',
Remove,
);
import {extend, render, Text} from '@shopify/admin-ui-extensions-react';

function Add() {
return <Text>Hello, world</Text>;
}
function Create() {
/* ... */
}
function Edit() {
/* ... */
}
function Remove() {
/* ... */
}

extend(
'Admin::Product::SubscriptionPlan::Add',
render(() => <Add />),
);
extend(
'Admin::Product::SubscriptionPlan::Create',
render(() => <Create />),
);
extend(
'Admin::Product::SubscriptionPlan::Edit',
render(() => <Edit />),
);
extend(
'Admin::Product::SubscriptionPlan::Remove',
render(() => <Remove />),
);

The response of an extension point will depend on the context in which it was triggered. Here are the expected responses:

Anchor to Product details pageProduct details page

Extension point expected responses in the Shopify admin product details page
Extension pointExpected reponse
Admin::Product::SubscriptionPlan::Add
  • productId: The id of the current product
  • variantId: null
Admin::Product::SubscriptionPlan::Create
  • productId: The id of the current product
  • variantId: null
Admin::Product::SubscriptionPlan::Edit
  • sellingPlanGroupId: The id of the selling plan group being edited
  • productId: The id of the current product
  • variantId: null
Admin::Product::SubscriptionPlan::Remove
  • sellingPlanGroupId: The id of the selling plan group being edited
  • productId: The id of the current product
  • variantId: null
  • variantIds: An array of the current product's child variant ids for which you should also remove the selling plan group association

Anchor to Variant details pageVariant details page

Extension point expected responses in the Shopify admin variant details page
Extension pointExpected reponse
Admin::Product::SubscriptionPlan::Add
  • productId: The id of the current variant's parent product
  • variantId: The id of the current variant
Admin::Product::SubscriptionPlan::Create
  • productId: The id of the current variant's parent product
  • variantId: The id of the current variant
Admin::Product::SubscriptionPlan::Edit
  • sellingPlanGroupId: The id of the selling plan group being edited
  • productId: The id of the current variant's parent product
  • variantId: The id of the current variant
Admin::Product::SubscriptionPlan::Remove
  • sellingPlanGroupId: The id of the selling plan group being edited
  • productId: The id of the current variant's parent product
  • variantId: The id of the current variant
  • variantIds: An empty array because the variant has no child variants

Your extension receives data from the host page. The Create callback function includes the data passed into the host page.

In the following example, the current product is being rendered inside the extension as (Product 1):

App Bridge Admin subscriptions app overlay

In the first line of the Create callback function, the data variable is assigned to the input data that's passed into the extension from the host page.

src/index.js

import {extend, Card} from '@shopify/admin-ui-extensions';

function Create(root, api) {
const data = api.data;

// ...

const planTitleCard = root.createComponent(Card, {
sectioned: true,
title: `Create subscription plan for Product id ${data.productId}`,
});
root.appendChild(planTitleCard);

root.mount();
}

extend('Admin::Product::SubscriptionPlan::Create', Create);
import {extend, render, useData, Card} from '@shopify/admin-ui-extensions-react';

function Create() {
const data = useData();

// ...

return (
// ...
<Card
title={`Create subscription plan for Product id ${data.productId}`}
sectioned
> ... </Card>
)
}

extend('Admin::Product::SubscriptionPlan::Create', render(() => <Create />);

The Create callback function renders the UI components that appear in the canvas of the app overlay.

The example renders the following UI components:

App Bridge Admin subscriptions app overlay

src/index.js

import {extend, Card, Text, TextField, InlineStack} from '@shopify/admin-ui-extensions';

function Create(api, root) {
// ...

const planDetailsCard = root.createComponent(Card, {
sectioned: true,
title: 'Delivery and discount',
});
root.appendChild(planDetailsCard);

const inlineStack = root.createComponent(InlineStack);
planDetailsCard.appendChild(inlineStack);

const deliveryFrequencyField = root.createComponent(TextField, {
type: 'number',
label: 'Delivery frequency (in weeks)',
value: undefined,
onChange(value) {
deliveryFrequencyField.updateProps({
value,
});
},
});
inlineStack.appendChild(deliveryFrequencyField);

const percentageOffField = root.createComponent(TextField, {
type: 'number',
label: 'Percentage off (%)',
value: undefined,
onChange(value) {
percentageOffField.updateProps({
value,
});
},
});
inlineStack.appendChild(percentageOffField);

const actionsElement = root.createComponent(InlineStack, {distribution: 'fill'});
root.appendChild(actionsElement);
actionsElement.appendChild(secondaryButton);

const primaryButtonStack = root.createComponent(InlineStack, {
distribution: 'trailing',
});
actionsElement.appendChild(primaryButtonStack);
primaryButtonStack.appendChild(primaryButton);

root.mount();
}

extend('Admin::Product::SubscriptionPlan::Create', Create);
import {extend, render, Card, Text, TextField, InlineStack} from '@shopify/admin-ui-extensions-react';

function Create() {
// ...

return (
<>
<Text size="titleLarge">Create plan</Text>
<Card title={`Create subscription plan for Product id ${data.productId}`} sectioned>
<TextField label="Plan title" value={planTitle} onAfterChange={setPlanTitle} />
</Card>
<Card title="Delivery and discount" sectioned>
<InlineStack>
<TextField
type="number"
label="Delivery frequency (in weeks)"
value={deliveryFrequency}
onAfterChange={setDeliveryFrequency}
/>
<TextField
type="number"
label="Percentage off (%)"
value={percentageOff}
onAfterChange={setPercentageOff}
/>
</InlineStack>
</Card>
{actions}
);
}

extend('Admin::Product::SubscriptionPlan::Create', render(() => <Create />);

For more information, refer to the following resources:



Was this page helpful?