658 lines
18 KiB
Markdown
658 lines
18 KiB
Markdown
---
|
||
title: Upgrading to 2025-10
|
||
description: >
|
||
This guide describes how to upgrade your admin UI extension to API version
|
||
`2025-10` and adopt web components.
|
||
source_url:
|
||
html: 'https://shopify.dev/docs/apps/build/admin/upgrading-to-2025-10'
|
||
md: 'https://shopify.dev/docs/apps/build/admin/upgrading-to-2025-10.md'
|
||
---
|
||
|
||
|
||
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',
|
||
() => <Extension />,
|
||
);
|
||
|
||
function Extension() {
|
||
return (
|
||
<AdminBlock title="Order status">
|
||
<BlockStack gap>
|
||
<TextField label="Tracking number" />
|
||
<Button title="Update" variant="primary" />
|
||
</BlockStack>
|
||
</AdminBlock>
|
||
);
|
||
}
|
||
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(<Extension />, document.body);
|
||
}
|
||
|
||
function Extension() {
|
||
// Data for this target comes through the global `shopify` object
|
||
const orderId = shopify.data.selected?.[0]?.id;
|
||
|
||
return (
|
||
<s-admin-block heading="Order summary">
|
||
<s-stack gap="base">
|
||
<s-text>Order ID: {orderId}</s-text>
|
||
<s-button variant="primary">Do something</s-button>
|
||
</s-stack>
|
||
</s-admin-block>
|
||
);
|
||
}
|
||
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(<Extension />, document.body).
|
||
UI is declared with Polaris web components:
|
||
<s-admin-block> (replaces AdminBlock, uses heading instead of title)
|
||
<s-stack> (replaces BlockStack)
|
||
<s-text>, <s-button>, 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](https://shopify.dev/docs/api/admin-extensions/latest/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
|
||
|
||
```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
|
||
|
||
```json
|
||
{
|
||
"dependencies": {
|
||
"preact": "^10.10.x",
|
||
"@preact/signals": "^2.3.x",
|
||
"@shopify/ui-extensions": "2025.10.x"
|
||
}
|
||
}
|
||
```
|
||
|
||
## Previous dependencies with React
|
||
|
||
## package.json
|
||
|
||
```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
|
||
|
||
```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
|
||
|
||
```json
|
||
{
|
||
"compilerOptions": {
|
||
"jsx": "react-jsx",
|
||
"jsxImportSource": "preact",
|
||
"target": "ES2020",
|
||
"checkJs": true,
|
||
"allowJs": true,
|
||
"moduleResolution": "node",
|
||
"esModuleInterop": true
|
||
}
|
||
}
|
||
```
|
||
|
||
## Old tsconfig.json
|
||
|
||
```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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```js
|
||
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
|
||
|
||
```tsx
|
||
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
|
||
|
||
```tsx
|
||
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
|
||
|
||
```ts
|
||
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
|
||
|
||
```tsx
|
||
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
|
||
|
||
```tsx
|
||
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
|
||
|
||
```tsx
|
||
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
|
||
|
||
```tsx
|
||
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
|
||
|
||
```ts
|
||
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](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components) equivalent.
|
||
|
||
| **Legacy component** | **Web component** | **Migration notes** |
|
||
| - | - | - |
|
||
| `AdminAction` | [`Admin action`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/settings-and-templates/admin-action) | Use `heading` instead of `title`. Primary and secondary actions are rendered as child elements. |
|
||
| `AdminBlock` | [`Admin block`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/settings-and-templates/admin-block) | Use `heading` instead of `title`. |
|
||
| `AdminPrintAction` | [`Admin print action`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/settings-and-templates/admin-print-action) | None |
|
||
| `Badge` | [`Badge`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/feedback-and-status-indicators/badge) | None |
|
||
| `Banner` | [`Banner`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/feedback-and-status-indicators/banner) | None |
|
||
| `BlockStack` | [`Stack`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/layout-and-structure/stack) | Use the stack component with default block direction. |
|
||
| `InlineStack` | [`Stack`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/layout-and-structure/stack) | Use the stack component with `direction="inline"`. |
|
||
| `Box` | [`Box`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/layout-and-structure/box) | None |
|
||
| `Button` | [`Button`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/actions/button) | None |
|
||
| `Checkbox` | [`Checkbox`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/checkbox) | None |
|
||
| `ChoiceList` | [`Choice list`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/choice-list) | None |
|
||
| `ColorPicker` | [`Color picker`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/color-picker) | None |
|
||
| `CustomerSegmentTemplate` | [Customer Segment Template Extension API](https://shopify.dev/docs/api/admin-extensions/2025-10/target-apis/contextual-apis/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`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/date-field) | None |
|
||
| `DatePicker` | [`Date picker`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/date-picker) | None |
|
||
| `Divider` | [`Divider`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/layout-and-structure/divider) | None |
|
||
| `EmailField` | [`Email field`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/email-field) | None |
|
||
| `Form` | [`Form`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/form) | None |
|
||
| `FunctionSettings` | [`Function settings`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/function-settings) | None |
|
||
| `Heading` | [`Heading`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/typography-and-content/heading) | None |
|
||
| `HeadingGroup` | | Removed. Use heading levels directly. |
|
||
| `Icon` | [`Icon`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/media-and-visuals/icon) | None |
|
||
| `Image` | [`Image`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/media-and-visuals/image) | None |
|
||
| `Link` | [`Link`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/actions/link) | None |
|
||
| `MoneyField` | [`Money field`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/money-field) | None |
|
||
| `NumberField` | [`Number field`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/number-field) | None |
|
||
| `Paragraph` | [`Paragraph`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/typography-and-content/paragraph) | None |
|
||
| `PasswordField` | [`Password field`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/password-field) | None |
|
||
| `Pressable` | [`Clickable`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/actions/clickable) | None |
|
||
| `ProgressIndicator` | [`Spinner`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/feedback-and-status-indicators/spinner) | None |
|
||
| `Section` | [`Section`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/layout-and-structure/section) | None |
|
||
| `Select` | [`Select`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/select) | None |
|
||
| `Text` | [`Text`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/typography-and-content/text) | None |
|
||
| `TextArea` | [`Text area`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/text-area) | None |
|
||
| `TextField` | [`Text field`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/text-field) | None |
|
||
| `UrlField` | [`URL field`](https://shopify.dev/docs/api/admin-extensions/2025-10/web-components/forms/url-field) | None |
|
||
|
||
*** |