Version 2025-07 is the last API version to support React-based UI components. Later versions use web components, native UI elements with built-in accessibility, better performance, and consistent styling with Shopify's design system. Check out the migration guide to upgrade your extension.
Form
The Form component wraps form controls and manages submission and reset behavior through the Shopify admin's save bar.
Unlike HTML forms, Form doesn't automatically submit data using HTTP—you must handle form data programmatically in your onSubmit callback. For Shopify Functions configuration forms, use FunctionSettings.
Supported targets
- admin.
abandoned-checkout-details. action. render - admin.
abandoned-checkout-details. block. render - admin.
catalog-details. action. render - admin.
catalog-details. block. render - admin.
collection-details. action. render - admin.
collection-details. block. render - admin.
collection-index. action. render - admin.
company-details. action. render - admin.
company-details. block. render - admin.
company-location-details. block. render - admin.
customer-details. action. render - admin.
customer-details. block. render - admin.
customer-index. action. render - admin.
customer-index. selection-action. render - admin.
customer-segment-details. action. render - admin.
discount-details. action. render - admin.
discount-details. function-settings. render - admin.
discount-index. action. render - admin.
draft-order-details. action. render - admin.
draft-order-details. block. render - admin.
draft-order-index. action. render - admin.
draft-order-index. selection-action. render - admin.
gift-card-details. action. render - admin.
gift-card-details. block. render - admin.
order-details. action. render - admin.
order-details. block. render - admin.
order-details. print-action. render - admin.
order-fulfilled-card. action. render - admin.
order-index. action. render - admin.
order-index. selection-action. render - admin.
order-index. selection-print-action. render - admin.
product-details. action. render - admin.
product-details. block. render - admin.
product-details. configuration. render - admin.
product-details. print-action. render - admin.
product-details. reorder. render - admin.
product-index. action. render - admin.
product-index. selection-action. render - admin.
product-index. selection-print-action. render - admin.
product-purchase-option. action. render - admin.
product-variant-details. action. render - admin.
product-variant-details. block. render - admin.
product-variant-details. configuration. render - admin.
product-variant-purchase-option. action. render - admin.
settings. order-routing-rule. render - admin.
settings. validation. render
Supported targets
- admin.
abandoned-checkout-details. action. render - admin.
abandoned-checkout-details. block. render - admin.
catalog-details. action. render - admin.
catalog-details. block. render - admin.
collection-details. action. render - admin.
collection-details. block. render - admin.
collection-index. action. render - admin.
company-details. action. render - admin.
company-details. block. render - admin.
company-location-details. block. render - admin.
customer-details. action. render - admin.
customer-details. block. render - admin.
customer-index. action. render - admin.
customer-index. selection-action. render - admin.
customer-segment-details. action. render - admin.
discount-details. action. render - admin.
discount-details. function-settings. render - admin.
discount-index. action. render - admin.
draft-order-details. action. render - admin.
draft-order-details. block. render - admin.
draft-order-index. action. render - admin.
draft-order-index. selection-action. render - admin.
gift-card-details. action. render - admin.
gift-card-details. block. render - admin.
order-details. action. render - admin.
order-details. block. render - admin.
order-details. print-action. render - admin.
order-fulfilled-card. action. render - admin.
order-index. action. render - admin.
order-index. selection-action. render - admin.
order-index. selection-print-action. render - admin.
product-details. action. render - admin.
product-details. block. render - admin.
product-details. configuration. render - admin.
product-details. print-action. render - admin.
product-details. reorder. render - admin.
product-index. action. render - admin.
product-index. selection-action. render - admin.
product-index. selection-print-action. render - admin.
product-purchase-option. action. render - admin.
product-variant-details. action. render - admin.
product-variant-details. block. render - admin.
product-variant-details. configuration. render - admin.
product-variant-purchase-option. action. render - admin.
settings. order-routing-rule. render - admin.
settings. validation. render
Anchor to PropertiesProperties
Props for the Form component, a container that groups related input fields and manages submission and reset behavior through the save bar.
- Anchor to onResetonResetonReset() => void | Promise<void>() => void | Promise<void>requiredrequired
A callback that fires when the form is reset. This is triggered by the save bar's Discard action. Use it to revert your extension's state back to its last saved values. Return a
Promiseif you need to perform async cleanup.- Anchor to onSubmitonSubmitonSubmit() => void | Promise<void>() => void | Promise<void>requiredrequired
A callback that fires when the form is submitted. This is triggered by the save bar's Save action or by a submit button inside the form. Return a
Promiseif you need to perform async work (like a network request), and the Shopify admin will wait for it to resolve before treating the submission as complete.- Anchor to idididstringstring
A unique identifier for the form. Use this when you need to reference the form from outside its tree, for example from a submit button that isn't a child of the form.
Anchor to ExamplesExamples
Anchor to Submit product metadata formSubmit product metadata form
Submit a warehouse SKU and storage location, or cancel to close the modal. This example uses Form with onSubmit and onReset callbacks around two TextField inputs, where onReset calls close() to dismiss the modal.
Submit product metadata form
 inputs, where `onReset` calls `close()` to dismiss the modal.](https://cdn.shopify.com/shopifycloud/shopify-dev/production/assets/assets/images/templated-apis-screenshots/admin-extensions/2025-07/form-default-CIQEVvTJ.png)
Submit product metadata form
React
import {reactExtension, useApi, Form, TextField, Button, BlockStack} from '@shopify/ui-extensions-react/admin';
function App() {
const {data, close} = useApi('admin.product-details.action.render');
const productId = data.selected[0]?.id;
return (
<Form
onSubmit={async () => {
await fetch('/api/products/metadata', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId}),
});
close();
}}
onReset={() => close()}
>
<BlockStack gap>
<TextField label="Warehouse SKU" name="warehouseSku" required />
<TextField label="Storage location" name="location" />
<Button variant="primary">Save product metadata</Button>
</BlockStack>
</Form>
);
}
export default reactExtension(
'admin.product-details.action.render',
() => <App />,
);TS
import {extension, Form, TextField, Button, BlockStack} from '@shopify/ui-extensions/admin';
export default extension(
'admin.product-details.action.render',
(root, api) => {
const {data, close} = api;
const productId = data.selected[0]?.id;
const form = root.createComponent(Form, {
onSubmit: async () => {
await fetch('/api/products/metadata', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId}),
});
close();
},
onReset: () => {
close();
},
});
const stack = root.createComponent(BlockStack, {gap: true});
const skuField = root.createComponent(TextField, {
label: 'Warehouse SKU',
name: 'warehouseSku',
required: true,
});
const locationField = root.createComponent(TextField, {
label: 'Storage location',
name: 'location',
});
const submitButton = root.createComponent(
Button,
{variant: 'primary'},
'Save product metadata',
);
stack.appendChild(skuField);
stack.appendChild(locationField);
stack.appendChild(submitButton);
form.appendChild(stack);
root.appendChild(form);
},
);Anchor to Handle form submit and cancelHandle form submit and cancel
Handle cancellation with the onReset callback to close the modal without saving. This example pairs submit and reset actions using InlineStack to right-align the cancel and save buttons, following standard dialog patterns.
Handle form submit and cancel
React
import {reactExtension, useApi, Form, TextField, Button, InlineStack, BlockStack} from '@shopify/ui-extensions-react/admin';
function App() {
const {data, close} = useApi('admin.product-details.action.render');
const productId = data.selected[0]?.id;
return (
<Form
onSubmit={async () => {
await fetch('/api/products/shipping', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId}),
});
close();
}}
onReset={() => close()}
>
<BlockStack gap>
<TextField label="Package weight (kg)" name="weight" />
<TextField label="Dimensions (L×W×H cm)" name="dimensions" />
<InlineStack gap inlineAlignment="end">
<Button variant="tertiary" accessibilityRole="reset">Cancel</Button>
<Button variant="primary" accessibilityRole="submit">Save shipping info</Button>
</InlineStack>
</BlockStack>
</Form>
);
}
export default reactExtension(
'admin.product-details.action.render',
() => <App />,
);TS
import {extension, Form, TextField, Button, InlineStack, BlockStack} from '@shopify/ui-extensions/admin';
export default extension(
'admin.product-details.action.render',
(root, api) => {
const {data, close} = api;
const productId = data.selected[0]?.id;
const form = root.createComponent(Form, {
onSubmit: async () => {
await fetch('/api/products/shipping', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId}),
});
close();
},
onReset: () => {
close();
},
});
const stack = root.createComponent(BlockStack, {gap: true});
const weightField = root.createComponent(TextField, {
label: 'Package weight (kg)',
name: 'weight',
});
const dimensionsField = root.createComponent(TextField, {
label: 'Dimensions (L×W×H cm)',
name: 'dimensions',
});
const actions = root.createComponent(InlineStack, {gap: true, inlineAlignment: 'end'});
const resetButton = root.createComponent(
Button,
{variant: 'tertiary', accessibilityRole: 'reset'},
'Cancel',
);
const submitButton = root.createComponent(
Button,
{variant: 'primary', accessibilityRole: 'submit'},
'Save shipping info',
);
actions.appendChild(resetButton);
actions.appendChild(submitButton);
stack.appendChild(weightField);
stack.appendChild(dimensionsField);
stack.appendChild(actions);
form.appendChild(stack);
root.appendChild(form);
},
);Anchor to Organize form with sectionsOrganize form with sections
Organize complex forms with Section components to group related fields together. This example splits a fulfillment provider setup into "Provider details" and "Contact information" sections, making long forms easier to scan.
Organize form with sections
React
import {reactExtension, useApi, Form, TextField, EmailField, Section, Button, BlockStack} from '@shopify/ui-extensions-react/admin';
function App() {
const {data, close} = useApi('admin.product-details.action.render');
const productId = data.selected[0]?.id;
return (
<Form
onSubmit={async () => {
await fetch('/api/fulfillment/setup', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId}),
});
close();
}}
onReset={() => close()}
>
<BlockStack gap>
<Section heading="Provider details">
<TextField label="Provider name" name="providerName" required />
<TextField label="API endpoint" name="endpoint" />
</Section>
<Section heading="Contact information">
<TextField label="Contact name" name="contactName" />
<EmailField label="Contact email" name="contactEmail" />
</Section>
<Button variant="primary">Set up provider</Button>
</BlockStack>
</Form>
);
}
export default reactExtension(
'admin.product-details.action.render',
() => <App />,
);TS
import {extension, Form, TextField, EmailField, Section, Button, BlockStack} from '@shopify/ui-extensions/admin';
export default extension(
'admin.product-details.action.render',
(root, api) => {
const {data, close} = api;
const productId = data.selected[0]?.id;
const form = root.createComponent(Form, {
onSubmit: async () => {
await fetch('/api/fulfillment/setup', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({productId}),
});
close();
},
onReset: () => {
close();
},
});
const stack = root.createComponent(BlockStack, {gap: true});
const providerSection = root.createComponent(Section, {heading: 'Provider details'});
const nameField = root.createComponent(TextField, {label: 'Provider name', name: 'providerName', required: true});
const endpointField = root.createComponent(TextField, {label: 'API endpoint', name: 'endpoint'});
providerSection.appendChild(nameField);
providerSection.appendChild(endpointField);
const contactSection = root.createComponent(Section, {heading: 'Contact information'});
const contactName = root.createComponent(TextField, {label: 'Contact name', name: 'contactName'});
const contactEmail = root.createComponent(EmailField, {label: 'Contact email', name: 'contactEmail'});
contactSection.appendChild(contactName);
contactSection.appendChild(contactEmail);
const submitButton = root.createComponent(Button, {variant: 'primary'}, 'Set up provider');
stack.appendChild(providerSection);
stack.appendChild(contactSection);
stack.appendChild(submitButton);
form.appendChild(stack);
root.appendChild(form);
},
);Anchor to LimitationsLimitations
- Form doesn't provide built-in form state management or validation. You must manage field values, errors, and dirty state yourself.
- The save bar appearance and behavior is controlled by the Shopify admin. You can't customize its position, text, or button labels.
- Form doesn't support nested forms. Only one Form component should be used per extension view.