18 KiB
title, description, source_url
| title | description | source_url | ||||
|---|---|---|---|---|---|---|
| Upgrading to 2025-10 | This guide describes how to upgrade your admin UI extension to API version `2025-10` and adopt web components. |
|
For a new admin order UI block (for example, admin.order-details.block.render), you should use Preact + Polaris web components, not React.
The current guidance in the admin UI extensions docs is:
Version 2025-07 is the last API version that supports React-based UI components. From 2025-10 and onward, Shopify recommends Preact and web components for admin UI extensions. See: Upgrading to 2025-10 (admin UI extensions, Preact & web components) Why Preact (web components) over React? From the upgrade guide for admin UI extensions (Upgrading to 2025-10):
React-based @shopify/ui-extensions-react components are being phased out. “Version 2025-07 is the last API version to support React-based UI components. Web components replace them…” The recommended stack going forward is: preact @preact/signals (optional but nice for reactive state) @shopify/ui-extensions (web components + APIs) New admin UI extensions (including order blocks) are scaffolded and documented with Preact by default. So:
If you’re starting anything new today (including admin order UI blocks): Use Preact + <s-*> web components. If you already have legacy React-based admin UI extensions: You can stay on an older API version (e.g. 2025-04/2025-07) short-term, But you’ll eventually need to upgrade to 2025-10 and migrate those to Preact + web components. What this looks like for admin.order-details.block.render In React (legacy), an order details block might have looked like this (from the upgrade guide):
tsx Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // LEGACY – React-based admin block (2025-07 or earlier) import { reactExtension, AdminBlock, BlockStack, TextField, Button, } from '@shopify/ui-extensions-react/admin';
export default reactExtension( 'admin.order-details.block.render', () => , );
function Extension() { return ( ); } For new work you should instead do something like:
tsx Copy 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import '@shopify/ui-extensions/preact'; import {render} from 'preact';
export default function extension() { render(, document.body); }
function Extension() {
// Data for this target comes through the global shopify object
const orderId = shopify.data.selected?.[0]?.id;
return ( Order ID: {orderId} Do something ); } This Preact/web-components example has been validated against the polaris-admin-extensions API for the admin.order-details.block.render target.
Key points:
Use import '@shopify/ui-extensions/preact'; and render from preact. The entry export is a function that calls render(, document.body). UI is declared with Polaris web components: (replaces AdminBlock, uses heading instead of title) (replaces BlockStack) , , etc. Data comes from the global shopify object, e.g. shopify.data.selected?.[0]?.id for the selected order. When might you still use React? You’d only stick with React if:
You have existing React-based admin UI extensions already in production. You need a short-term, low-risk fix and don’t want to migrate immediately. You’re okay staying on an older API version (≤ 2025-07) for a while. Even in that case, Shopify’s docs explicitly push you toward:
Upgrading API version to 2025-10 in shopify.extension.toml. Adjusting your package.json dependencies to use Preact instead of React. See the “Adjust package dependencies” section in Upgrading to 2025-10. Practical recommendation for your admin order block For an admin order UI block (e.g. admin.order-details.block.render):
Scaffold the extension with the CLI (as recommended): bash Copy 1 shopify app generate extension --template admin_block --name my-admin-order-block In the generated extension:
Ensure api_version in shopify.extension.toml is set to 2025-10 (or later). Use Preact + web components in your Extension.jsx/tsx. Only consider React if:
You’re locked to an older API version for now, and You plan a future migration to Preact/web components.
Upgrading to 2025-10
This guide describes how to upgrade your admin UI extension to API version 2025-10 and adopt web components from Polaris, Shopify's unified system for building app interfaces.
Version 2025-07 is the last API version to support React-based UI components. Web components replace them with native UI elements that offer built-in accessibility, better performance, and consistent styling — so your extension looks and behaves like the rest of the Shopify admin.
Update API version
Set the API version to 2025-10 in shopify.extension.toml to use web components.
shopify.extension.toml
api_version = "2025-10"
[[extensions]]
name = "your-extension"
handle = "your-extension"
type = "ui_extension"
uid = "ab22fe63-a741-cbc6-90c1-fbcf94a84426b9cbbe1f"
# Contents of your existing file...
Adjust package dependencies
As of 2025-10, Shopify recommends Preact for UI extensions. Update the dependencies in your package.json file and re-install.
New dependencies with Preact
package.json
{
"dependencies": {
"preact": "^10.10.x",
"@preact/signals": "^2.3.x",
"@shopify/ui-extensions": "2025.10.x"
}
}
Previous dependencies with React
package.json
{
"dependencies": {
"react": "^18.0.0",
"@shopify/ui-extensions": "2025.4.x",
"@shopify/ui-extensions-react": "2025.4.x",
"react-reconciler": "0.29.0"
},
"devDependencies": {
"@types/react": "^18.0.0"
}
}
Previous dependencies with JavaScript
package.json
{
"dependencies": {
"@shopify/ui-extensions": "2025.4.x"
}
}
TypeScript configuration
Get full IntelliSense and auto-complete support by adding a config file for your extension at extensions/{extension-name}/tsconfig.json. You don't need to change your app's root tsconfig.json file.
New tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact",
"target": "ES2020",
"checkJs": true,
"allowJs": true,
"moduleResolution": "node",
"esModuleInterop": true
}
}
Old tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx"
},
"include": ["./src"]
}
Upgrade the Shopify CLI
The new CLI adds support for building 2025-10 extensions.
The shopify app dev command runs your app and also generates a shopify.d.ts file in your extension directory, adding support for the new global shopify object.
Support new global shopify object
# Upgrade to latest version of the CLI
npm install -g @shopify/cli
# Run the app to generate the type definition file
shopify app dev
Optional ESLint configuration
If your app uses ESLint, update your configuration to include the new global shopify object.
.eslintrc.cjs
module.exports = {
globals: {
shopify: 'readonly',
},
};
Migrate API calls
Instead of accessing APIs from a callback parameter or React hook, access them from the global shopify object. Here's an example of migrating API calls for an admin block.
New API calls in Preact
import '@shopify/ui-extensions/preact';
import {render} from 'preact';
export default function extension() {
render(<Extension />, document.body);
}
function Extension() {
const productId = shopify.data.selected?.[0]?.id;
return (
<s-admin-block heading="Product information">
<s-text>Product ID: {productId}</s-text>
</s-admin-block>
);
}
Previous API calls in React
import {
reactExtension,
useApi,
AdminBlock,
Text,
} from '@shopify/ui-extensions-react/admin';
export default reactExtension(
'admin.product-details.block.render',
() => <Extension />,
);
function Extension() {
const {data} = useApi();
const productId = data.selected?.[0]?.id;
return (
<AdminBlock title="Product information">
<Text>Product ID: {productId}</Text>
</AdminBlock>
);
}
Previous API calls in JavaScript
import {extension, AdminBlock, Text} from '@shopify/ui-extensions/admin';
export default extension(
'admin.product-details.block.render',
(root, api) => {
const productId = api.data.selected?.[0]?.id;
const adminBlock = root.createComponent(
AdminBlock,
{title: 'Product information'},
[root.createComponent(Text, {}, `Product ID: ${productId}`)],
);
root.appendChild(adminBlock);
root.mount();
},
);
Migrate hooks
If you were previously using React hooks, import those same hooks from a Preact-specific package. Here's an example of migrating hooks for an admin action.
New hooks in Preact
import '@shopify/ui-extensions/preact';
import {render} from 'preact';
import {useState} from 'preact/hooks';
export default function extension() {
render(<Extension />, document.body);
}
function Extension() {
const [title, setTitle] = useState('');
async function handleSubmit() {
const productId = shopify.data.selected?.[0]?.id;
await fetch('shopify:admin/api/graphql.json', {
method: 'POST',
body: JSON.stringify({
query: `mutation SetMetafield($input: [MetafieldsSetInput!]!) {
metafieldsSet(metafields: $input) { metafields { id } }
}`,
variables: {
input: [{
ownerId: productId,
namespace: 'my-app',
key: 'title',
type: 'single_line_text_field',
value: title,
}],
},
}),
});
shopify.close();
}
return (
<s-admin-action heading="Set title">
<s-text-field
label="Title"
value={title}
onInput={(e) => setTitle(e.target.value)}
/>
<s-button variant="primary" onClick={handleSubmit}>
Save
</s-button>
</s-admin-action>
);
}
Previous hooks in React
import React, {useState} from 'react';
import {
reactExtension,
useApi,
AdminAction,
Button,
TextField,
} from '@shopify/ui-extensions-react/admin';
export default reactExtension(
'admin.product-details.action.render',
() => <Extension />,
);
function Extension() {
const {close, data, query} = useApi();
const [title, setTitle] = useState('');
async function handleSubmit() {
const productId = data.selected?.[0]?.id;
await query(
`mutation SetMetafield($input: [MetafieldsSetInput!]!) {
metafieldsSet(metafields: $input) { metafields { id } }
}`,
{
variables: {
input: [{
ownerId: productId,
namespace: 'my-app',
key: 'title',
type: 'single_line_text_field',
value: title,
}],
},
},
);
close();
}
return (
<AdminAction title="Set title" primaryAction={<Button onPress={handleSubmit}>Save</Button>}>
<TextField label="Title" value={title} onChange={setTitle} />
</AdminAction>
);
}
Migrate to web components
Web components are exposed as custom HTML elements. Update your React components to custom elements.
New components in Preact
import '@shopify/ui-extensions/preact';
import {render} from 'preact';
export default function extension() {
render(<Extension />, document.body);
}
function Extension() {
return (
<s-admin-block heading="Order status">
<s-stack gap="base">
<s-text-field label="Tracking number"></s-text-field>
<s-button variant="primary">Update</s-button>
</s-stack>
</s-admin-block>
);
}
Previous components in React
import {
reactExtension,
AdminBlock,
BlockStack,
TextField,
Button,
} from '@shopify/ui-extensions-react/admin';
export default reactExtension(
'admin.order-details.block.render',
() => <Extension />,
);
function Extension() {
return (
<AdminBlock title="Order status">
<BlockStack gap>
<TextField label="Tracking number" />
<Button title="Update" variant="primary" />
</BlockStack>
</AdminBlock>
);
}
Previous components in JavaScript
import {
extension,
AdminBlock,
BlockStack,
TextField,
Button,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.order-details.block.render',
(root, _api) => {
root.replaceChildren(
root.createComponent(
AdminBlock,
{title: 'Order status'},
[
root.createComponent(BlockStack, {gap: true}, [
root.createComponent(TextField, {label: 'Tracking number'}),
root.createComponent(Button, {
title: 'Update',
variant: 'primary',
}),
]),
],
),
);
},
);
Web components mapping
The following table maps each legacy React component to its web component equivalent.
| Legacy component | Web component | Migration notes |
|---|---|---|
AdminAction |
Admin action |
Use heading instead of title. Primary and secondary actions are rendered as child elements. |
AdminBlock |
Admin block |
Use heading instead of title. |
AdminPrintAction |
Admin print action |
None |
Badge |
Badge |
None |
Banner |
Banner |
None |
BlockStack |
Stack |
Use the stack component with default block direction. |
InlineStack |
Stack |
Use the stack component with direction="inline". |
Box |
Box |
None |
Button |
Button |
None |
Checkbox |
Checkbox |
None |
ChoiceList |
Choice list |
None |
ColorPicker |
Color picker |
None |
CustomerSegmentTemplate |
Customer Segment Template Extension API | Replaced by a target API. Return template data from the admin.customers.segmentation-templates.data target instead of rendering a component. |
DateField |
Date field |
None |
DatePicker |
Date picker |
None |
Divider |
Divider |
None |
EmailField |
Email field |
None |
Form |
Form |
None |
FunctionSettings |
Function settings |
None |
Heading |
Heading |
None |
HeadingGroup |
Removed. Use heading levels directly. | |
Icon |
Icon |
None |
Image |
Image |
None |
Link |
Link |
None |
MoneyField |
Money field |
None |
NumberField |
Number field |
None |
Paragraph |
Paragraph |
None |
PasswordField |
Password field |
None |
Pressable |
Clickable |
None |
ProgressIndicator |
Spinner |
None |
Section |
Section |
None |
Select |
Select |
None |
Text |
Text |
None |
TextArea |
Text area |
None |
TextField |
Text field |
None |
UrlField |
URL field |
None |