diff --git a/admin-purchase-options-action/README.md.liquid b/admin-purchase-options-action/README.md.liquid
new file mode 100644
index 0000000..eb3322c
--- /dev/null
+++ b/admin-purchase-options-action/README.md.liquid
@@ -0,0 +1,8 @@
+# Admin purchase options extension
+
+Purchase options extensions allow developers to integrate custom functionality into the Shopify Admin interface,
+enabling merchants to create and edit purchase options directly on product and product variant pages. These extensions
+provide interactive elements that enhance the merchant's ability to manage purchase options efficiently.
+
+Learn more about Admin action extensions in Shopify’s [developer
+documentation](https://shopify.dev/docs/apps/admin/admin-actions-and-blocks).
diff --git a/admin-purchase-options-action/locales/en.default.json.liquid b/admin-purchase-options-action/locales/en.default.json.liquid
new file mode 100644
index 0000000..0e62981
--- /dev/null
+++ b/admin-purchase-options-action/locales/en.default.json.liquid
@@ -0,0 +1,4 @@
+{
+ "name": "{{ name }}",
+ "welcome": "Welcome to the {% raw %}{{TARGET}}{% endraw %} extension!"
+}
diff --git a/admin-purchase-options-action/locales/fr.json.liquid b/admin-purchase-options-action/locales/fr.json.liquid
new file mode 100644
index 0000000..3fdcc1e
--- /dev/null
+++ b/admin-purchase-options-action/locales/fr.json.liquid
@@ -0,0 +1,4 @@
+{
+ "name": "{{ name }}",
+ "welcome": "Bienvenue dans l'extension {% raw %}{{TARGET}}{% endraw %}!"
+}
diff --git a/admin-purchase-options-action/package.json.liquid b/admin-purchase-options-action/package.json.liquid
new file mode 100644
index 0000000..82ca981
--- /dev/null
+++ b/admin-purchase-options-action/package.json.liquid
@@ -0,0 +1,27 @@
+{%- if flavor contains "react" -%}
+{
+ "name": "{{ handle }}",
+ "private": true,
+ "version": "1.0.0",
+ "license": "UNLICENSED",
+ "dependencies": {
+ "react": "^18.0.0",
+ "@shopify/ui-extensions": "unstable",
+ "@shopify/ui-extensions-react": "unstable"
+ },
+ "devDependencies": {
+ "@types/react": "^18.0.0",
+ "react-reconciler": "0.29.0"
+ }
+}
+{%- else -%}
+{
+ "name": "{{ handle }}",
+ "private": true,
+ "version": "1.0.0",
+ "license": "UNLICENSED",
+ "dependencies": {
+ "@shopify/ui-extensions": "unstable"
+ }
+}
+{%- endif -%}
diff --git a/admin-purchase-options-action/shopify.extension.toml.liquid b/admin-purchase-options-action/shopify.extension.toml.liquid
new file mode 100644
index 0000000..8c575e0
--- /dev/null
+++ b/admin-purchase-options-action/shopify.extension.toml.liquid
@@ -0,0 +1,27 @@
+api_version = "unstable"
+
+[[extensions]]
+# Change the merchant-facing name of the extension in locales/en.default.json
+name = "t:name"
+handle = "{{ handle }}"
+type = "ui_extension"
+{% if uid %}uid = "{{ uid }}"{% endif %}
+
+# Both targets need to be specified for the extensions to work
+[[extensions.targeting]]
+module = "./src/ProductExtension.{{ srcFileExtension }}"
+# The target used here must match the target used in the module file (./src/PurchaseOptionsActionExtension.{{ srcFileExtension }})
+target = "admin.product-purchase-option.action.render"
+
+[[extensions.targeting]]
+module = "./src/ProductVariantExtension.{{ srcFileExtension }}"
+# The target used here must match the target used in the module file (./src/PurchaseOptionsActionExtension.{{ srcFileExtension }})
+target = "admin.product-variant-purchase-option.action.render"
+
+# Valid extension targets:
+
+# Product index and detail pages
+# - admin.product-purchase-option.action.render
+
+# Product variant detail pages
+# - admin.product-variant-purchase-option.action.render
diff --git a/admin-purchase-options-action/src/ProductExtension.liquid b/admin-purchase-options-action/src/ProductExtension.liquid
new file mode 100644
index 0000000..d71f395
--- /dev/null
+++ b/admin-purchase-options-action/src/ProductExtension.liquid
@@ -0,0 +1,28 @@
+{%- if flavor contains "react" -%}
+import {reactExtension} from '@shopify/ui-extensions-react/admin';
+import PurchaseOptionsActionExtension from './PurchaseOptionsActionExtension';
+
+export default reactExtension('admin.product-purchase-option.action.render', () => (
+
+));
+
+{%- else -%}
+import {extension} from '@shopify/ui-extensions/admin';
+import PurchaseOptionsActionExtension from './PurchaseOptionsActionExtension';
+
+export default extension(
+ 'admin.product-purchase-option.action.render',
+ (root, {i18n, close, data}) => {
+ PurchaseOptionsActionExtension(
+ 'admin.product-purchase-option.action.render',
+ root,
+ {
+ i18n,
+ close,
+ data,
+ },
+ );
+ },
+);
+
+{%- endif -%}
diff --git a/admin-purchase-options-action/src/ProductVariantExtension.liquid b/admin-purchase-options-action/src/ProductVariantExtension.liquid
new file mode 100644
index 0000000..c72e98e
--- /dev/null
+++ b/admin-purchase-options-action/src/ProductVariantExtension.liquid
@@ -0,0 +1,28 @@
+{%- if flavor contains "react" -%}
+import {reactExtension} from '@shopify/ui-extensions-react/admin';
+import PurchaseOptionsActionExtension from './PurchaseOptionsActionExtension';
+
+export default reactExtension('admin.product-variant-purchase-option.action.render', () => (
+
+));
+
+{%- else -%}
+import {extension} from '@shopify/ui-extensions/admin';
+import PurchaseOptionsActionExtension from './PurchaseOptionsActionExtension';
+
+export default extension(
+ 'admin.product-variant-purchase-option.action.render',
+ (root, {i18n, close, data}) => {
+ PurchaseOptionsActionExtension(
+ 'admin.product-variant-purchase-option.action.render',
+ root,
+ {
+ i18n,
+ close,
+ data,
+ },
+ );
+ },
+);
+
+{%- endif -%}
\ No newline at end of file
diff --git a/admin-purchase-options-action/src/PurchaseOptionsActionExtension.liquid b/admin-purchase-options-action/src/PurchaseOptionsActionExtension.liquid
new file mode 100644
index 0000000..e2d5965
--- /dev/null
+++ b/admin-purchase-options-action/src/PurchaseOptionsActionExtension.liquid
@@ -0,0 +1,297 @@
+{%- if flavor contains "react" -%}
+import {useState} from 'react';
+import {
+ useApi,
+ AdminAction,
+ BlockStack,
+ Button,
+ TextField,
+ ChoiceList,
+ Box,
+ InlineStack,
+ NumberField,
+ Select,
+} from '@shopify/ui-extensions-react/admin';
+
+export default function PurchaseOptionsActionExtension(extension) {
+ // The useApi hook provides access to several useful APIs like i18n, close, and data.
+ const {i18n, close, data} = useApi(extension);
+ console.log({data});
+ const [merchantCode, setMerchantCode] = useState('');
+ const [planName, setPlanName] = useState('');
+ const [discountType, setDiscountType] = useState('percentageOff');
+ const [deliveryOptions, setDeliveryOptions] = useState({
+ frequency: 0,
+ timeType: 'day',
+ discount: 0,
+ });
+
+ const updateDeliveryOption = (field, value) => {
+ setDeliveryOptions((prevOptions) => ({
+ ...prevOptions,
+ [field]: value,
+ }));
+ };
+
+ function handleSave() {
+ // This is where you can use the sellingPlanGroupsCreate and sellingPlanGroupsUpdate mutations
+ console.log('saving');
+ close();
+ }
+
+ function getDiscountLabel(discountType) {
+ switch (discountType) {
+ case 'percentageOff':
+ return 'Percentage off';
+ case 'amountOff':
+ return 'Amount off';
+ case 'flatRate':
+ return 'Flat rate';
+ }
+ }
+
+ return (
+ Save}
+ secondaryAction={
+
+ }
+ >
+
+ {i18n.translate('welcome', {extension})}
+
+
+
+ setDiscountType(typeof e === 'string' ? e : e[0])}
+ />
+
+
+
+ updateDeliveryOption('frequency', value)}
+ />
+
+
+
+
+ );
+}
+
+{%- else -%}
+import {
+ AdminAction,
+ BlockStack,
+ Box,
+ Button,
+ InlineStack,
+ Text,
+ TextField,
+ NumberField,
+ Select,
+ ChoiceList,
+} from '@shopify/ui-extensions/admin';
+
+export default function PurchaseOptionsAction(
+ extensions,
+ root,
+ {i18n, close, data},
+) {
+ let title = '';
+ let internalDescription = '';
+ let deliveryFrequencyValue = 1;
+ let deliveryDiscountValue = 0;
+ let deliveryFrequencyUnit = 'week';
+ let discountType = 'percentageOff';
+ console.log('Data', data);
+
+ const handleSave = () => {
+ console.log('Saving', {
+ title,
+ internalDescription,
+ deliveryFrequencyValue,
+ deliveryFrequencyUnit,
+ deliveryDiscountValue,
+ });
+ close();
+ };
+
+ const handleClose = () => {
+ console.log('Closing');
+ close();
+ };
+
+ const getDiscountLabel = (discountType) => {
+ switch (discountType) {
+ case 'percentageOff':
+ return 'Percentage off';
+ case 'amountOff':
+ return 'Amount off';
+ case 'flatRate':
+ return 'Flat rate';
+ default:
+ return 'Discount';
+ }
+ };
+
+ const titleField = root.createComponent(TextField, {
+ label: 'Title',
+ value: title,
+ placeholder: 'Subscribe and save',
+ helpText:
+ 'Customers will see this on storefront product pages that have subscriptions',
+ onChange: (value) => {
+ title = value;
+ },
+ });
+
+ const internalDescriptionField = root.createComponent(TextField, {
+ label: 'Internal description',
+ value: internalDescription,
+ helpText: 'For your reference only',
+ onChange: (value) => {
+ internalDescription = value;
+ },
+ });
+
+ const discountField = root.createComponent(NumberField, {
+ label: getDiscountLabel(discountType),
+ value: deliveryDiscountValue,
+ onChange: (value) => {
+ deliveryDiscountValue = value;
+ },
+ });
+
+ const discountTypeChoiceList = root.createComponent(ChoiceList, {
+ title: 'Discount type',
+ selected: discountType,
+ value: discountType,
+ onChange: (value) => {
+ discountType = value;
+ discountField.updateProps({label: getDiscountLabel(discountType)});
+ root.update();
+ },
+ choices: [
+ {label: 'Percentage off', id: 'percentageOff'},
+ {label: 'Amount off', id: 'amountOff'},
+ {label: 'Flat rate', id: 'flatRate'},
+ ],
+ });
+
+ const deliveryFrequencyField = root.createComponent(NumberField, {
+ label: 'Delivery frequency',
+ value: deliveryFrequencyValue,
+ onChange: (value) => {
+ deliveryFrequencyValue = value;
+ },
+ });
+
+ const deliveryFrequencyUnitSelect = root.createComponent(Select, {
+ label: 'Delivery interval',
+ value: deliveryFrequencyUnit,
+ options: [
+ {label: 'Week', value: 'week'},
+ {label: 'Month', value: 'month'},
+ {label: 'Year', value: 'year'},
+ ],
+ onChange: (value) => {
+ deliveryFrequencyUnit = value;
+ },
+ });
+
+ root.append(
+ root.createComponent(
+ AdminAction,
+ {
+ primaryAction: root.createComponent(
+ Button,
+ {onPress: handleSave},
+ 'Done',
+ ),
+ secondaryAction: root.createComponent(
+ Button,
+ {onPress: handleClose},
+ 'Close',
+ ),
+ },
+ root.createComponent(
+ BlockStack,
+ {gap: 'large'},
+ root.createComponent(
+ Text,
+ {fontWeight: 'bold'},
+ i18n.translate('welcome', {extensions}),
+ ),
+ titleField,
+ internalDescriptionField,
+ root.createComponent(Box, null, discountTypeChoiceList),
+ root.createComponent(
+ Box,
+ null,
+ root.createComponent(
+ InlineStack,
+ {
+ gap: true,
+ inlineAlignment: 'end',
+ blockAlignment: 'end',
+ },
+ deliveryFrequencyField,
+ deliveryFrequencyUnitSelect,
+ discountField,
+ ),
+ ),
+ ),
+ ),
+ );
+}
+{%- endif -%}
diff --git a/admin-purchase-options-action/tsconfig.json b/admin-purchase-options-action/tsconfig.json
new file mode 100644
index 0000000..b1ce5a0
--- /dev/null
+++ b/admin-purchase-options-action/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ // This tsconfig.json file is only needed to inform the IDE
+ // About the `react-jsx` tsconfig option, so IDE doesn't complain about missing react import
+ // Changing options here won't affect the build of your extension
+ "compilerOptions": {
+ "jsx": "react-jsx"
+ },
+ "include": ["./src"]
+}