Skip to main content

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.


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...

Anchor to Adjust package dependenciesAdjust 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"
}
}

Anchor to TypeScript configurationTypeScript 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"]
}

Anchor to Upgrade the Shopify CLIUpgrade 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

Anchor to Optional ESLint configurationOptional ESLint configuration

If your app uses ESLint, update your configuration to include the new global shopify object.

.eslintrc.cjs

module.exports = {
globals: {
shopify: 'readonly',
},
};

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();
},
);

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>
);
}

Anchor to Migrate to web componentsMigrate 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',
}),
]),
],
),
);
},
);

Anchor to Web components mappingWeb components mapping

The following table maps each legacy React component to its web component equivalent.

Legacy componentWeb componentMigration notes
AdminActionAdmin actionUse heading instead of title. Primary and secondary actions are rendered as child elements.
AdminBlockAdmin blockUse heading instead of title.
AdminPrintActionAdmin print actionNone
BadgeBadgeNone
BannerBannerNone
BlockStackStackUse the stack component with default block direction.
InlineStackStackUse the stack component with direction="inline".
BoxBoxNone
ButtonButtonNone
CheckboxCheckboxNone
ChoiceListChoice listNone
ColorPickerColor pickerNone
CustomerSegmentTemplateCustomer Segment Template Extension APIReplaced by a target API. Return template data from the admin.customers.segmentation-templates.data target instead of rendering a component.
DateFieldDate fieldNone
DatePickerDate pickerNone
DividerDividerNone
EmailFieldEmail fieldNone
FormFormNone
FunctionSettingsFunction settingsNone
HeadingHeadingNone
HeadingGroupRemoved. Use heading levels directly.
IconIconNone
ImageImageNone
LinkLinkNone
MoneyFieldMoney fieldNone
NumberFieldNumber fieldNone
ParagraphParagraphNone
PasswordFieldPassword fieldNone
PressableClickableNone
ProgressIndicatorSpinnerNone
SectionSectionNone
SelectSelectNone
TextTextNone
TextAreaText areaNone
TextFieldText fieldNone
UrlFieldURL fieldNone

Was this page helpful?