Admin UI extensions
Admin UI extensions make it possible to surface contextual app functionality within the Shopify Admin interface.
Anchor to overviewOverview
Extend the Shopify Admin with UI Extensions.

Anchor to getting-startedGetting Started
Use the Shopify CLI to generate a new extension within your app.
If you already have a Shopify app, you can skip right to the last command shown here.
Make sure you’re using Shopify CLI v3.80.0
or higher. You can check your version by running shopify version
.
Generate an extension
CLI
Examples
Generate an extension
CLI
# create an app if you don't already have one: POLARIS_UNIFIED=true shopify app init --name my-app # navigate to your app's root directory: cd my-app # generate a new extension: POLARIS_UNIFIED=true shopify app generate extension # follow the steps to create a new # extension in ./extensions.
Anchor to scaffolded-with-preactScaffolded with Preact
UI Extensions are scaffolded with Preact by default.
This means that you can use Preact patterns and principles within your extension. Since Preact is included as a standard dependency, you have access to all of its features including hooks like and
for managing component state and side effects.
You can also use Preact Signals for reactive state management, and take advantage of standard web APIs just like you would in a regular Preact application.
Scaffolded with Preact
JSX
Examples
Scaffolded with Preact
JSX
import '@shopify/ui-extensions/preact'; import {render} from 'preact'; import {useState} from 'preact/hooks'; export default async () => { render(<Extension />, document.body); } function Extension() { const [count, setCount] = useState(0); return ( <> <s-text>Count: {count}</s-text> <s-button onClick={() => setCount(count + 1)}> Increment </s-button> </> ); }
Anchor to handling-eventsHandling events
Handling events in UI extensions are the same as you would handle them in a web app. You can use the method to listen for events on the components or use the
on[event]
property to listen for events from the components.
When using Preact, event handlers can be registered by passing props beginning with on
, and the event handler name is case-insensitive. For example, the JSX registers
fn
as a "click" event listener on the button.
Handling events
JSX
Examples
Handling events
JSX
export default function HandlingEvents() { const handleClick = () => { console.log('s-button clicked'); }; return <s-button onClick={handleClick}>Click me</s-button>; } // or export default function HandlingEvents() { const handleClick = () => { console.log('s-button clicked'); }; const button = document.createElement('s-button'); button.addEventListener('click', handleClick); document.body.appendChild(button); }
Anchor to using-formsUsing Forms
When building a Block extension you may use the Form component to integrate with the contextual save bar of the resource page. The Form component provides a way to manage form state and submit data to your app's backend or directly to Shopify using Direct API access.
Whenever an input field is changed, the Form component will automatically update the dirty state of the resource page. When the form is submitted or reset the relevant callback in the form component will get triggered.
Using this, you can control what defines a component to be dirty by utilizing the Input's defaultValue property.
Rules:
When the defaultValue is set, the component will be considered dirty if the value of the input is different from the defaultValue.You may update the defaultValue when the form is submitted to reset the dirty state of the form.
When the defaultValue is not set, the component will be considered dirty if the value of the input is different from the initial value or from the last dynamic update to the input's value that wasn't triggered by user input.
Note: In order to trigger the dirty state, each input must have a name attribute.
Trigger the Form's dirty state
Examples
Trigger the Form's dirty state
Using `defaultValue`
import { render } from 'preact'; import { useState } from 'preact/hooks'; export default async () => { render(<Extension />, document.body); } const defaultValues = { text: 'default value', number: 50, }; function Extension() { const [textValue, setTextValue] = useState(''); const [numberValue, setNumberValue] = useState(''); return ( <s-admin-block title="My Block Extension"> <s-form onSubmit={(event) => { event.waitUntil(fetch('app:save/data')); console.log('submit', {textValue, numberValue}); } onReset={() => console.log('automatically reset values')} > <s-stack direction="block" gap="base"> <s-text-field label="Default Value" name="my-text" defaultValue={defaultValues.text} value={textValue} onChange={(e) => setTextValue(e.target.value)} /> <s-number-field label="Percentage field" name="my-number" defaultValue={defaultValues.number} value={numberValue} onChange={(e) => setNumberValue(e.target.value)} /> </s-stack> </s-form> </s-admin-block> ); }
Using implicit default
import { render } from 'preact'; import { useState } from 'preact/hooks'; export default async () => { render(<Extension />, document.body); } async function Extension() { const data = await fetch('/data.json'); const {text, number} = await data.json(); return <App text={text} number={number} />; } function App({text, number}) { // The initial values set in the form fields will be the default values const [textValue, setTextValue] = useState(text); const [numberValue, setNumberValue] = useState(number); return ( <s-admin-block title="My Block Extension"> <s-form onSubmit={(event) => { event.waitUntil(fetch('app:data/save')); console.log('submit', {textValue, numberValue}); } onReset={() => console.log('automatically reset values')} > <s-stack direction="block" gap="base"> <s-text-field label="Default Value" name="my-text" value={textValue} onChange={(e) => setTextValue(e.target.value)} /> <s-number-field label="Percentage field" name="my-number" value={numberValue} onChange={(e) => setNumberValue(e.target.value)} /> </s-stack> </s-form> </s-admin-block> ); }
Anchor to picking-resourcesPicking Resources
Use the Resource Picker and Picker API's to allow users to select resources for your extension to use.
Anchor to picking-resources-resource-pickerResource Picker
Use the API to display a search-based interface to help users find and select one or more products, collections, or product variants, and then return the selected resources to your extension. Both the app and the user must have the necessary permissions to access the resources selected.
Use the picker
API to display a search-based interface to help users find and select one or more custom data types that you provide, such as product reviews, email templates, or subscription options.
ResourcePicker
Selecting a product
Examples
resourcePicker
Selecting a product
import { render } from 'preact'; export default async () => { render(<Extension />, document.body); } function Extension() { const handleSelectProduct = async () => { const selected = await shopify.resourcePicker({ type: 'product' }); console.log(selected); }; return <s-button onClick={handleSelectProduct}>Select product</s-button>; }


Anchor to deployingDeploying
Use the Shopify CLI to deploy your app and its extensions.
Deploy an extension
CLI
Examples
Deploy an extension
CLI
# navigate to your app's root directory: cd my-app # deploy your app and its extensions: shopify app deploy # follow the steps to deploy