---
title: Customers
description: >-
Customer pages display information about individual customers, customer lists,
and customer segments. Extensions on these pages help merchants enhance
customer relationships, manage customer data, and build targeted marketing
campaigns.
api_version: 2025-07
api_name: admin-extensions
source_url:
html: 'https://shopify.dev/docs/api/admin-extensions/2025-07/targets/customers'
md: 'https://shopify.dev/docs/api/admin-extensions/2025-07/targets/customers.md'
---
Migrate to Polaris
Version 2025-07 is the last API version to support React-based UI components. Later versions use [web components](https://shopify.dev/docs/api/admin-extensions/latest/web-components), native UI elements with built-in accessibility, better performance, and consistent styling with [Shopify's design system](https://shopify.dev/docs/apps/design). Check out the [migration guide](https://shopify.dev/docs/apps/build/admin/upgrading-to-2025-10) to upgrade your extension.
# Customers
Customer pages display information about individual customers, customer lists, and [customer segments](https://help.shopify.com/manual/customers/customer-segmentation). Extensions on these pages help merchants enhance customer relationships, manage customer data, and build targeted marketing campaigns.
### Use cases
* **Enhanced customer insights:** Display customer insights from external CRM systems, loyalty platforms, or analytics tools to provide merchants with a complete view of customer behavior and preferences.
* **Marketing workflows:** Enable merchants to export customer segments to email marketing platforms, create targeted campaigns, or sync customer data with advertising networks for personalized outreach.
* **Loyalty and rewards:** Show customer loyalty status, points balances, tier information, or special perks directly within the customer profile to help merchants provide better service.
* **Data quality and verification:** Verify customer information, flag duplicate records, enhance customer profiles with additional data from third-party sources, or validate addresses and contact details.
* **Bulk customer operations:** Process multiple customers at once for operations like tagging, exporting, updating custom fields, or triggering workflows in external systems.

***
## Customer details targets
Use [action and block targets](https://shopify.dev/docs/api/admin-extensions/2025-07#building-your-extension) to extend the customer details page. Add workflows and contextual information that help merchants manage customer relationships and access integrated customer data.
Action targets open as modal overlays from the **More actions** menu, while block targets display as inline cards. The examples demonstrate fetching data from Shopify's [direct API](https://shopify.dev/docs/api/admin-extensions/2025-07#direct-api-access) or your [app's backend](https://shopify.dev/docs/api/admin-extensions/2025-07#app-authentication).
### Customer details action target
`admin.customer-details.action.render`
Renders an admin action extension on the customer details page. Merchants can access this extension from the **More actions** menu. Use this target to provide workflows that operate on individual customer data, such as exporting customer information, syncing with CRM systems, or managing loyalty programs.
Extensions at this target can access information about the customer through the `data` property in the [Action Extension API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/core-apis/action-extension-api). The action renders in a modal overlay, providing space for multi-step workflows, forms, and confirmations.
### Support Components (35) APIs (1)
### Supported components
* [AdminAction](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminaction)
* [AdminBlock](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminblock)
* [AdminPrintAction](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminprintaction)
* [Badge](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/badge)
* [Banner](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/banner)
* [BlockStack](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/blockstack)
* [Box](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/box)
* [Button](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/button)
* [Checkbox](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/checkbox)
* [ChoiceList](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/choicelist)
* [ColorPicker](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/colorpicker)
* [DateField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/datefield)
* [DatePicker](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/datepicker)
* [Divider](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/divider)
* [EmailField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/emailfield)
* [Form](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/form)
* [FunctionSettings](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/functionsettings)
* [Heading](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/heading)
* [HeadingGroup](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/headinggroup)
* [Icon](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/media-and-visuals/icon)
* [Image](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/media-and-visuals/image)
* [InlineStack](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/inlinestack)
* [Link](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/link)
* [MoneyField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/moneyfield)
* [NumberField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/numberfield)
* [Paragraph](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/paragraph)
* [PasswordField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/passwordfield)
* [Pressable](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/pressable)
* [ProgressIndicator](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/progressindicator)
* [Section](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/section)
* [Select](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/select)
* [Text](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/text)
* [TextArea](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/textarea)
* [TextField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/textfield)
* [URLField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/urlfield)
### Available APIs
* [Action Extension API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/core-apis/action-extension-api)
Examples
### Examples
* ####
##### Description
Add an action extension that exports customer data to an external CRM system. This example shows how to create a modal workflow that fetches customer details and sends them to a third-party service.
##### React
```tsx
import React from 'react';
import {useState} from 'react';
import {
reactExtension,
useApi,
AdminAction,
Banner,
Section,
BlockStack,
Checkbox,
Button,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customer-details.action.render';
export default reactExtension(TARGET, () => );
function App() {
const {data, close, query} = useApi(TARGET);
const [loading, setLoading] = useState(false);
const [syncOrders, setSyncOrders] = useState(true);
const [syncMetafields, setSyncMetafields] = useState(false);
const [success, setSuccess] = useState(false);
const [error, setError] = useState(false);
const handleExport = async () => {
setLoading(true);
setSuccess(false);
setError(false);
const customerId = data.selected[0].id;
try {
// Fetch customer details from GraphQL Admin API
const {data: customerData} = await query(
`
query GetCustomer($id: ID!) {
customer(id: $id) {
id
firstName
lastName
email
phone
ordersCount
amountSpent {
amount
currencyCode
}
tags
addresses {
address1
address2
city
province
country
zip
}
}
}
`,
{variables: {id: customerId}}
);
// Export to CRM through your app's backend
const response = await fetch('https://your-app.com/api/export-customer', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
customer: customerData.customer,
includeOrders: syncOrders,
includeMetafields: syncMetafields,
}),
});
if (response.ok) {
setSuccess(true);
close();
} else {
setError(true);
}
} catch (err) {
setError(true);
} finally {
setLoading(false);
}
};
return (
{loading ? 'Exporting...' : 'Export Customer'}
}
secondaryAction={
}
>
{success && (
Customer exported successfully!
)}
{error && (
Failed to export customer. Please try again.
)}
Include order history
Include metafield data
);
}
```
##### TS
```ts
import {
extension,
AdminAction,
Banner,
Section,
BlockStack,
Checkbox,
Button,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-details.action.render',
(root, api) => {
let loading = false;
let syncOrders = true;
let syncMetafields = false;
let success = false;
let error = false;
const handleExport = async () => {
loading = true;
success = false;
error = false;
updateUI();
const customerId = api.data.selected[0].id;
try {
// Fetch customer details from GraphQL Admin API
const {data: customerData} = await api.query(
`
query GetCustomer($id: ID!) {
customer(id: $id) {
id
firstName
lastName
email
phone
ordersCount
amountSpent {
amount
currencyCode
}
tags
addresses {
address1
address2
city
province
country
zip
}
}
}
`,
{variables: {id: customerId}}
);
// Export to CRM through your app's backend
const response = await fetch('https://your-app.com/api/export-customer', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
customer: customerData.customer,
includeOrders: syncOrders,
includeMetafields: syncMetafields,
}),
});
if (response.ok) {
success = true;
updateUI();
api.close();
} else {
error = true;
updateUI();
}
} catch (err) {
error = true;
updateUI();
} finally {
loading = false;
updateUI();
}
};
const primaryAction = root.createFragment();
const secondaryAction = root.createFragment();
const content = root.createFragment();
const updateUI = () => {
content.replaceChildren();
if (success) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'success', dismissible: true},
'Customer exported successfully!'
)
);
}
if (error) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'critical', dismissible: true},
'Failed to export customer. Please try again.'
)
);
}
const section = root.createComponent(Section, {heading: 'Export options'});
const blockStack = root.createComponent(BlockStack);
blockStack.appendChild(
root.createComponent(
Checkbox,
{
checked: syncOrders,
onChange: (value) => {
syncOrders = value;
},
},
'Include order history'
)
);
blockStack.appendChild(
root.createComponent(
Checkbox,
{
checked: syncMetafields,
onChange: (value) => {
syncMetafields = value;
},
},
'Include metafield data'
)
);
section.appendChild(blockStack);
content.appendChild(section);
};
primaryAction.appendChild(
root.createComponent(
Button,
{
onPress: handleExport,
disabled: loading || success,
},
loading ? 'Exporting...' : 'Export Customer'
)
);
secondaryAction.appendChild(
root.createComponent(Button, {onPress: () => api.close()}, 'Cancel')
);
updateUI();
const adminAction = root.createComponent(
AdminAction,
{
title: 'Export to CRM',
primaryAction,
secondaryAction,
}
);
adminAction.appendChild(content);
root.appendChild(adminAction);
root.mount();
}
);
```
* ####
##### Description
Add an action extension that updates customer loyalty status in an external loyalty platform. This example demonstrates how to use the \[GraphQL Admin API]\(/docs/api/admin-graphql) to fetch customer information and update their loyalty tier.
##### React
```tsx
import React from 'react';
import {useState, useEffect} from 'react';
import {
reactExtension,
useApi,
AdminAction,
Banner,
Section,
BlockStack,
Box,
Select,
Text,
Button,
ProgressIndicator,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customer-details.action.render';
export default reactExtension(TARGET, () => );
function App() {
const {data, close} = useApi(TARGET);
const [loading, setLoading] = useState(false);
const [fetching, setFetching] = useState(true);
const [currentTier, setCurrentTier] = useState('');
const [newTier, setNewTier] = useState('');
const [success, setSuccess] = useState(false);
const [error, setError] = useState(false);
const tiers = [
{value: 'Bronze', label: 'Bronze'},
{value: 'Silver', label: 'Silver'},
{value: 'Gold', label: 'Gold'},
{value: 'Platinum', label: 'Platinum'},
];
useEffect(() => {
const fetchLoyaltyStatus = async () => {
const customerId = data.selected[0].id;
try {
// Fetch current loyalty status from your app's backend
const response = await fetch(
`https://your-app.com/api/loyalty-status?customerId=${customerId}`
);
const loyaltyData = await response.json();
setCurrentTier(loyaltyData.tier);
setNewTier(loyaltyData.tier);
} catch (err) {
console.error('Error fetching loyalty status:', err);
} finally {
setFetching(false);
}
};
fetchLoyaltyStatus();
}, [data]);
const handleUpdate = async () => {
setLoading(true);
setSuccess(false);
setError(false);
const customerId = data.selected[0].id;
try {
// Update loyalty tier through your app's backend
const response = await fetch('https://your-app.com/api/update-loyalty', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
customerId,
tier: newTier,
}),
});
if (response.ok) {
setSuccess(true);
close();
} else {
setError(true);
}
} catch (err) {
setError(true);
} finally {
setLoading(false);
}
};
if (fetching) {
return (
Loading loyalty information...
);
}
return (
{loading ? 'Updating...' : 'Update Tier'}
}
secondaryAction={
}
>
{success && (
Loyalty status updated successfully!
)}
{error && (
Failed to update loyalty status. Please try again.
)}
Current tier:
{currentTier}
);
}
```
##### TS
```ts
import {
extension,
AdminAction,
Banner,
Section,
BlockStack,
Box,
Select,
Text,
Button,
ProgressIndicator,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-details.action.render',
async (root, api) => {
const customerId = api.data.selected[0].id;
let loading = false;
let currentTier = '';
let newTier = '';
let success = false;
let error = false;
const tiers = [
{value: 'Bronze', label: 'Bronze'},
{value: 'Silver', label: 'Silver'},
{value: 'Gold', label: 'Gold'},
{value: 'Platinum', label: 'Platinum'},
];
const adminAction = root.createComponent(AdminAction, {title: 'Update Loyalty Status'});
// Show loading state
const loadingStack = root.createComponent(BlockStack);
loadingStack.appendChild(root.createComponent(ProgressIndicator, {size: 'small-100'}));
loadingStack.appendChild(root.createComponent(Text, {}, 'Loading loyalty information...'));
adminAction.appendChild(loadingStack);
root.appendChild(adminAction);
root.mount();
try {
// Fetch current loyalty status from your app's backend
const response = await fetch(
`https://your-app.com/api/loyalty-status?customerId=${customerId}`
);
const loyaltyData = await response.json();
currentTier = loyaltyData.tier;
newTier = loyaltyData.tier;
} catch (err) {
console.error('Error fetching loyalty status:', err);
}
const handleUpdate = async () => {
loading = true;
success = false;
error = false;
updateUI();
try {
// Update loyalty tier through your app's backend
const response = await fetch('https://your-app.com/api/update-loyalty', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
customerId,
tier: newTier,
}),
});
if (response.ok) {
success = true;
updateUI();
api.close();
} else {
error = true;
updateUI();
}
} catch (err) {
error = true;
updateUI();
} finally {
loading = false;
updateUI();
}
};
const primaryAction = root.createFragment();
const secondaryAction = root.createFragment();
const content = root.createFragment();
const updateUI = () => {
content.replaceChildren();
primaryAction.replaceChildren();
secondaryAction.replaceChildren();
if (success) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'success', dismissible: true},
'Loyalty status updated successfully!'
)
);
}
if (error) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'critical', dismissible: true},
'Failed to update loyalty status. Please try again.'
)
);
}
const section = root.createComponent(Section, {heading: 'Loyalty tier'});
const blockStack = root.createComponent(BlockStack);
const currentTierBox = root.createComponent(Box);
currentTierBox.appendChild(
root.createComponent(Text, {fontWeight: 'bold'}, 'Current tier: ')
);
currentTierBox.appendChild(root.createComponent(Text, {}, currentTier));
blockStack.appendChild(currentTierBox);
blockStack.appendChild(
root.createComponent(Select, {
label: 'New tier',
value: newTier,
onChange: (value) => {
newTier = value;
},
options: tiers,
})
);
section.appendChild(blockStack);
content.appendChild(section);
primaryAction.appendChild(
root.createComponent(
Button,
{
onPress: handleUpdate,
disabled: loading || success || newTier === currentTier,
},
loading ? 'Updating...' : 'Update Tier'
)
);
secondaryAction.appendChild(
root.createComponent(Button, {onPress: () => api.close()}, 'Cancel')
);
};
// Clear loading state and show form
adminAction.replaceChildren();
updateUI();
adminAction.setProps({
title: 'Update Loyalty Status',
primaryAction,
secondaryAction,
});
adminAction.appendChild(content);
}
);
```
### Customer details action (should render) target
`admin.customer-details.action.should-render`
Controls the render state of an admin action extension on the customer details page. Use this target to conditionally show or hide your action extension based on the customer's properties, such as order count, tags, or loyalty status.
This target returns a boolean value that determines whether the corresponding action extension appears in the **More actions** menu. The extension is evaluated each time the page loads.
### Support Components (0) APIs (1)
### Supported components
\-
### Available APIs
* [Should Render API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/utility-apis/should-render-api)
Examples
### Examples
* ####
##### Description
Conditionally display an action only for customers tagged as VIP. This example demonstrates how to use the \`should-render\` target to control extension visibility based on customer tags.
##### React
```tsx
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-details.action.should-render',
async (root, api) => {
const customerId = api.data.selected[0].id;
try {
// Fetch customer tags from GraphQL Admin API
const {data} = await api.query(
`
query GetCustomer($id: ID!) {
customer(id: $id) {
tags
}
}
`,
{variables: {id: customerId}}
);
const tags = data.customer.tags;
// Only show action for customers with VIP tag
return {render: tags.includes('VIP')};
} catch (err) {
console.error('Error fetching customer:', err);
return {render: false};
}
}
);
```
##### TS
```ts
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-details.action.should-render',
async (root, api) => {
const customerId = api.data.selected[0].id;
try {
// Fetch customer tags from GraphQL Admin API
const {data} = await api.query(
`
query GetCustomer($id: ID!) {
customer(id: $id) {
tags
}
}
`,
{variables: {id: customerId}}
);
const tags = data.customer.tags;
// Only show action for customers with VIP tag
return {render: tags.includes('VIP')};
} catch (err) {
console.error('Error fetching customer:', err);
return {render: false};
}
}
);
```
* ####
##### Description
Conditionally display the action only for customers who have placed more than one order. This example demonstrates filtering based on order count using the \[GraphQL Admin API]\(/docs/api/admin-graphql).
##### React
```tsx
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-details.action.should-render',
async (root, api) => {
const customerId = api.data.selected[0].id;
try {
// Fetch customer order count
const {data} = await api.query(
`
query GetCustomer($id: ID!) {
customer(id: $id) {
ordersCount
}
}
`,
{variables: {id: customerId}}
);
const ordersCount = data.customer.ordersCount;
// Only show action for repeat customers
return {render: ordersCount > 1};
} catch (err) {
console.error('Error fetching customer:', err);
return {render: false};
}
}
);
```
##### TS
```ts
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-details.action.should-render',
async (root, api) => {
const customerId = api.data.selected[0].id;
try {
// Fetch customer order count
const {data} = await api.query(
`
query GetCustomer($id: ID!) {
customer(id: $id) {
ordersCount
}
}
`,
{variables: {id: customerId}}
);
const ordersCount = data.customer.ordersCount;
// Only show action for repeat customers
return {render: ordersCount > 1};
} catch (err) {
console.error('Error fetching customer:', err);
return {render: false};
}
}
);
```
### Customer details block target
`admin.customer-details.block.render`
Renders an admin block extension inline on the customer details page. Use this target to display contextual information, analytics, or status updates related to the customer without requiring merchants to open a modal.
Extensions at this target appear as cards on the page and can show real-time data, insights, or quick actions. Blocks provide persistent visibility and are ideal for displaying information merchants need to see at a glance.
### Support Components (35) APIs (1)
### Supported components
* [AdminAction](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminaction)
* [AdminBlock](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminblock)
* [AdminPrintAction](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminprintaction)
* [Badge](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/badge)
* [Banner](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/banner)
* [BlockStack](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/blockstack)
* [Box](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/box)
* [Button](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/button)
* [Checkbox](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/checkbox)
* [ChoiceList](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/choicelist)
* [ColorPicker](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/colorpicker)
* [DateField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/datefield)
* [DatePicker](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/datepicker)
* [Divider](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/divider)
* [EmailField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/emailfield)
* [Form](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/form)
* [FunctionSettings](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/functionsettings)
* [Heading](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/heading)
* [HeadingGroup](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/headinggroup)
* [Icon](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/media-and-visuals/icon)
* [Image](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/media-and-visuals/image)
* [InlineStack](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/inlinestack)
* [Link](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/link)
* [MoneyField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/moneyfield)
* [NumberField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/numberfield)
* [Paragraph](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/paragraph)
* [PasswordField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/passwordfield)
* [Pressable](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/pressable)
* [ProgressIndicator](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/progressindicator)
* [Section](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/section)
* [Select](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/select)
* [Text](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/text)
* [TextArea](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/textarea)
* [TextField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/textfield)
* [URLField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/urlfield)
### Available APIs
* [Block Extension API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/core-apis/block-extension-api)
Examples
### Examples
* ####
##### Description
Create a block extension that shows customer loyalty status, points balance, and tier information. This example demonstrates how to present valuable loyalty insights inline on the customer page.
##### React
```tsx
import React from 'react';
import {useState, useEffect} from 'react';
import {
reactExtension,
useApi,
AdminBlock,
BlockStack,
Box,
Badge,
Heading,
InlineStack,
Text,
Divider,
ProgressIndicator,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customer-details.block.render';
export default reactExtension(TARGET, () => );
function App() {
const {data} = useApi(TARGET);
const [loyaltyData, setLoyaltyData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchLoyaltyData = async () => {
const customerId = data.selected[0].id;
try {
// Fetch loyalty data from your app's backend
const response = await fetch(
`https://your-app.com/api/loyalty-info?customerId=${customerId}`
);
const loyaltyInfo = await response.json();
setLoyaltyData(loyaltyInfo);
} catch (err) {
console.error('Error fetching loyalty data:', err);
} finally {
setLoading(false);
}
};
fetchLoyaltyData();
}, [data]);
if (loading) {
return (
Loading loyalty information...
);
}
if (!loyaltyData) {
return (
Unable to load loyalty information
);
}
return (
{loyaltyData.tier} Tier
Points Balance
{loyaltyData.points.toLocaleString()}
points
Lifetime Value
${loyaltyData.lifetimeValue.toLocaleString()}
Member Since
{loyaltyData.memberSince}
{loyaltyData.nextTier && (
<>
Progress to {loyaltyData.nextTier}
Spend ${loyaltyData.nextTierRequired} more to unlock
>
)}
);
}
```
##### TS
```ts
import {
extension,
AdminBlock,
BlockStack,
Box,
Badge,
Heading,
InlineStack,
Text,
Divider,
ProgressIndicator,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-details.block.render',
async (root, api) => {
const customerId = api.data.selected[0].id;
const adminBlock = root.createComponent(AdminBlock, {title: 'Loyalty Status'});
// Show loading state
const loadingStack = root.createComponent(BlockStack);
loadingStack.appendChild(root.createComponent(ProgressIndicator, {size: 'small-100'}));
loadingStack.appendChild(root.createComponent(Text, {}, 'Loading loyalty information...'));
adminBlock.appendChild(loadingStack);
root.appendChild(adminBlock);
root.mount();
try {
// Fetch loyalty data from your app's backend
const response = await fetch(
`https://your-app.com/api/loyalty-info?customerId=${customerId}`
);
const loyaltyData = await response.json();
// Clear loading state and show loyalty information
adminBlock.replaceChildren();
const blockStack = root.createComponent(BlockStack);
// Tier badge
const tierBox = root.createComponent(Box);
tierBox.appendChild(
root.createComponent(
Badge,
{tone: loyaltyData.tier === 'Platinum' ? 'success' : 'info'},
`${loyaltyData.tier} Tier`
)
);
blockStack.appendChild(tierBox);
blockStack.appendChild(root.createComponent(Divider));
// Points Balance
const pointsBox = root.createComponent(Box);
pointsBox.appendChild(root.createComponent(Heading, {}, 'Points Balance'));
const pointsStack = root.createComponent(InlineStack, {blockAlignment: 'center'});
pointsStack.appendChild(
root.createComponent(Text, {fontWeight: 'bold'}, loyaltyData.points.toLocaleString())
);
pointsStack.appendChild(root.createComponent(Text, {tone: 'subdued'}, 'points'));
pointsBox.appendChild(pointsStack);
blockStack.appendChild(pointsBox);
blockStack.appendChild(root.createComponent(Divider));
// Lifetime Value
const valueBox = root.createComponent(Box);
valueBox.appendChild(root.createComponent(Heading, {}, 'Lifetime Value'));
valueBox.appendChild(
root.createComponent(Text, {}, `$${loyaltyData.lifetimeValue.toLocaleString()}`)
);
blockStack.appendChild(valueBox);
blockStack.appendChild(root.createComponent(Divider));
// Member Since
const memberBox = root.createComponent(Box);
memberBox.appendChild(root.createComponent(Heading, {}, 'Member Since'));
memberBox.appendChild(root.createComponent(Text, {tone: 'subdued'}, loyaltyData.memberSince));
blockStack.appendChild(memberBox);
// Next tier progress (if applicable)
if (loyaltyData.nextTier) {
blockStack.appendChild(root.createComponent(Divider));
const nextTierBox = root.createComponent(Box);
nextTierBox.appendChild(
root.createComponent(Heading, {}, `Progress to ${loyaltyData.nextTier}`)
);
nextTierBox.appendChild(
root.createComponent(
Text,
{tone: 'subdued'},
`Spend $${loyaltyData.nextTierRequired} more to unlock`
)
);
blockStack.appendChild(nextTierBox);
}
adminBlock.appendChild(blockStack);
} catch (err) {
console.error('Error fetching loyalty data:', err);
adminBlock.replaceChildren(
root.createComponent(Text, {}, 'Unable to load loyalty information')
);
}
}
);
```
* ####
##### Description
Create a block extension that shows customer information from an external CRM system. This example demonstrates how to enhance the customer profile with additional insights from third-party platforms.
##### React
```tsx
import React from 'react';
import {useState, useEffect} from 'react';
import {
reactExtension,
useApi,
AdminBlock,
BlockStack,
Box,
Badge,
Heading,
Text,
Divider,
Button,
ProgressIndicator,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customer-details.block.render';
export default reactExtension(TARGET, () => );
function App() {
const {data} = useApi(TARGET);
const [crmData, setCrmData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchCrmData = async () => {
const customerId = data.selected[0].id;
try {
// Fetch CRM data from your app's backend
const response = await fetch(
`https://your-app.com/api/crm-data?customerId=${customerId}`
);
const crmInfo = await response.json();
setCrmData(crmInfo);
} catch (err) {
console.error('Error fetching CRM data:', err);
} finally {
setLoading(false);
}
};
fetchCrmData();
}, [data]);
if (loading) {
return (
);
}
if (!crmData) {
return (
No CRM data available
);
}
return (
Account Status
{crmData.status}
Last Contact
{crmData.lastContact}
via {crmData.contactMethod}
Assigned Sales Rep
{crmData.salesRep}
Open Tickets
{crmData.openTickets} active support tickets
{crmData.notes && (
<>
Recent Notes
{crmData.notes}
>
)}
);
}
```
##### TS
```ts
import {
extension,
AdminBlock,
BlockStack,
Box,
Badge,
Heading,
Text,
Divider,
Button,
ProgressIndicator,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-details.block.render',
async (root, api) => {
const customerId = api.data.selected[0].id;
const adminBlock = root.createComponent(AdminBlock, {title: 'CRM Information'});
// Show loading state
adminBlock.appendChild(root.createComponent(ProgressIndicator, {size: 'small-100'}));
root.appendChild(adminBlock);
root.mount();
try {
// Fetch CRM data from your app's backend
const response = await fetch(
`https://your-app.com/api/crm-data?customerId=${customerId}`
);
const crmData = await response.json();
// Clear loading state and show CRM information
adminBlock.replaceChildren();
const blockStack = root.createComponent(BlockStack);
// Account Status
const statusBox = root.createComponent(Box);
statusBox.appendChild(root.createComponent(Heading, {}, 'Account Status'));
statusBox.appendChild(
root.createComponent(
Badge,
{tone: crmData.status === 'Active' ? 'success' : 'warning'},
crmData.status
)
);
blockStack.appendChild(statusBox);
blockStack.appendChild(root.createComponent(Divider));
// Last Contact
const contactBox = root.createComponent(Box);
contactBox.appendChild(root.createComponent(Heading, {}, 'Last Contact'));
contactBox.appendChild(root.createComponent(Text, {}, crmData.lastContact));
contactBox.appendChild(
root.createComponent(Text, {tone: 'subdued'}, `via ${crmData.contactMethod}`)
);
blockStack.appendChild(contactBox);
blockStack.appendChild(root.createComponent(Divider));
// Sales Rep
const repBox = root.createComponent(Box);
repBox.appendChild(root.createComponent(Heading, {}, 'Assigned Sales Rep'));
repBox.appendChild(root.createComponent(Text, {}, crmData.salesRep));
blockStack.appendChild(repBox);
blockStack.appendChild(root.createComponent(Divider));
// Open Tickets
const ticketsBox = root.createComponent(Box);
ticketsBox.appendChild(root.createComponent(Heading, {}, 'Open Tickets'));
ticketsBox.appendChild(
root.createComponent(Text, {}, `${crmData.openTickets} active support tickets`)
);
blockStack.appendChild(ticketsBox);
// Notes (if present)
if (crmData.notes) {
blockStack.appendChild(root.createComponent(Divider));
const notesBox = root.createComponent(Box);
notesBox.appendChild(root.createComponent(Heading, {}, 'Recent Notes'));
notesBox.appendChild(root.createComponent(Text, {tone: 'subdued'}, crmData.notes));
blockStack.appendChild(notesBox);
}
// View in CRM button
blockStack.appendChild(
root.createComponent(
Button,
{
onPress: () => window.open(crmData.crmUrl, '_blank'),
variant: 'secondary',
},
'View in CRM'
)
);
adminBlock.appendChild(blockStack);
} catch (err) {
console.error('Error fetching CRM data:', err);
adminBlock.replaceChildren(root.createComponent(Text, {}, 'No CRM data available'));
}
}
);
```
***
## Customer index targets
Use [action targets](https://shopify.dev/docs/api/admin-extensions/2025-07#building-your-extension) to extend the customer index page with bulk operations and workflows that help merchants manage multiple customers efficiently.
### Customer index action target
`admin.customer-index.action.render`
Renders an admin action extension on the customer index page. Merchants can access this extension from the **More actions** menu. Use this target to provide workflows that operate on the customer list, such as exporting all customers, generating reports, or setting up batch operations.
Extensions at this target can access the page context through the [Action Extension API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/core-apis/action-extension-api). The action renders in a modal overlay, providing space for configuration and execution of list-wide operations.
### Support Components (35) APIs (1)
### Supported components
* [AdminAction](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminaction)
* [AdminBlock](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminblock)
* [AdminPrintAction](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminprintaction)
* [Badge](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/badge)
* [Banner](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/banner)
* [BlockStack](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/blockstack)
* [Box](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/box)
* [Button](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/button)
* [Checkbox](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/checkbox)
* [ChoiceList](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/choicelist)
* [ColorPicker](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/colorpicker)
* [DateField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/datefield)
* [DatePicker](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/datepicker)
* [Divider](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/divider)
* [EmailField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/emailfield)
* [Form](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/form)
* [FunctionSettings](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/functionsettings)
* [Heading](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/heading)
* [HeadingGroup](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/headinggroup)
* [Icon](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/media-and-visuals/icon)
* [Image](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/media-and-visuals/image)
* [InlineStack](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/inlinestack)
* [Link](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/link)
* [MoneyField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/moneyfield)
* [NumberField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/numberfield)
* [Paragraph](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/paragraph)
* [PasswordField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/passwordfield)
* [Pressable](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/pressable)
* [ProgressIndicator](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/progressindicator)
* [Section](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/section)
* [Select](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/select)
* [Text](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/text)
* [TextArea](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/textarea)
* [TextField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/textfield)
* [URLField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/urlfield)
### Available APIs
* [Action Extension API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/core-apis/action-extension-api)
Examples
### Examples
* ####
##### Description
Add an action extension that generates a comprehensive customer report. This example shows how to create a modal workflow that allows merchants to configure and generate reports from the customer list.
##### React
```tsx
import React from 'react';
import {useState} from 'react';
import {
reactExtension,
useApi,
AdminAction,
Banner,
Section,
BlockStack,
Select,
Button,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customer-index.action.render';
export default reactExtension(TARGET, () => );
function App() {
const {close} = useApi(TARGET);
const [loading, setLoading] = useState(false);
const [reportType, setReportType] = useState('summary');
const [dateRange, setDateRange] = useState('30days');
const [success, setSuccess] = useState(false);
const [error, setError] = useState(false);
const reportTypes = [
{value: 'summary', label: 'Customer Summary'},
{value: 'lifetime-value', label: 'Lifetime Value Analysis'},
{value: 'retention', label: 'Retention Report'},
{value: 'segmentation', label: 'Segmentation Analysis'},
];
const dateRanges = [
{value: '7days', label: 'Last 7 days'},
{value: '30days', label: 'Last 30 days'},
{value: '90days', label: 'Last 90 days'},
{value: '1year', label: 'Last year'},
{value: 'all', label: 'All time'},
];
const handleGenerate = async () => {
setLoading(true);
setSuccess(false);
setError(false);
try {
// Generate report through your app's backend
const response = await fetch('https://your-app.com/api/generate-report', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
reportType,
dateRange,
}),
});
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `customer-report-${Date.now()}.pdf`;
a.click();
setSuccess(true);
close();
} else {
setError(true);
}
} catch (err) {
setError(true);
} finally {
setLoading(false);
}
};
return (
{loading ? 'Generating...' : 'Generate Report'}
}
secondaryAction={
}
>
{success && (
Report generated successfully!
)}
{error && (
Failed to generate report. Please try again.
)}
);
}
```
##### TS
```ts
import {
extension,
AdminAction,
Banner,
Section,
BlockStack,
Select,
Button,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-index.action.render',
(root, api) => {
let loading = false;
let reportType = 'summary';
let dateRange = '30days';
let success = false;
let error = false;
const reportTypes = [
{value: 'summary', label: 'Customer Summary'},
{value: 'lifetime-value', label: 'Lifetime Value Analysis'},
{value: 'retention', label: 'Retention Report'},
{value: 'segmentation', label: 'Segmentation Analysis'},
];
const dateRanges = [
{value: '7days', label: 'Last 7 days'},
{value: '30days', label: 'Last 30 days'},
{value: '90days', label: 'Last 90 days'},
{value: '1year', label: 'Last year'},
{value: 'all', label: 'All time'},
];
const handleGenerate = async () => {
loading = true;
success = false;
error = false;
updateUI();
try {
// Generate report through your app's backend
const response = await fetch('https://your-app.com/api/generate-report', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
reportType,
dateRange,
}),
});
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `customer-report-${Date.now()}.pdf`;
a.click();
success = true;
updateUI();
api.close();
} else {
error = true;
updateUI();
}
} catch (err) {
error = true;
updateUI();
} finally {
loading = false;
updateUI();
}
};
const primaryAction = root.createFragment();
const secondaryAction = root.createFragment();
const content = root.createFragment();
const updateUI = () => {
content.replaceChildren();
if (success) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'success', dismissible: true},
'Report generated successfully!'
)
);
}
if (error) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'critical', dismissible: true},
'Failed to generate report. Please try again.'
)
);
}
const section = root.createComponent(Section, {heading: 'Report configuration'});
const blockStack = root.createComponent(BlockStack);
blockStack.appendChild(
root.createComponent(Select, {
label: 'Report type',
value: reportType,
onChange: (value) => {
reportType = value;
},
options: reportTypes,
})
);
blockStack.appendChild(
root.createComponent(Select, {
label: 'Date range',
value: dateRange,
onChange: (value) => {
dateRange = value;
},
options: dateRanges,
})
);
section.appendChild(blockStack);
content.appendChild(section);
};
primaryAction.appendChild(
root.createComponent(
Button,
{
onPress: handleGenerate,
disabled: loading || success,
},
loading ? 'Generating...' : 'Generate Report'
)
);
secondaryAction.appendChild(
root.createComponent(Button, {onPress: () => api.close()}, 'Cancel')
);
updateUI();
const adminAction = root.createComponent(
AdminAction,
{
title: 'Generate Customer Report',
primaryAction,
secondaryAction,
}
);
adminAction.appendChild(content);
root.appendChild(adminAction);
root.mount();
}
);
```
* ####
##### Description
Add an action extension that syncs all customers to an external marketing or analytics platform. This example shows how to create a workflow that initiates a background sync job for the entire customer list.
##### React
```tsx
import React from 'react';
import {useState} from 'react';
import {
reactExtension,
useApi,
AdminAction,
Banner,
Section,
BlockStack,
Select,
Text,
Button,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customer-index.action.render';
export default reactExtension(TARGET, () => );
function App() {
const {close} = useApi(TARGET);
const [loading, setLoading] = useState(false);
const [platform, setPlatform] = useState('mailchimp');
const [syncType, setSyncType] = useState('full');
const [success, setSuccess] = useState(false);
const [error, setError] = useState(false);
const platforms = [
{value: 'mailchimp', label: 'Mailchimp'},
{value: 'klaviyo', label: 'Klaviyo'},
{value: 'hubspot', label: 'HubSpot'},
{value: 'salesforce', label: 'Salesforce'},
];
const syncTypes = [
{value: 'full', label: 'Full sync (all customers)'},
{value: 'incremental', label: 'Incremental (changes only)'},
{value: 'new', label: 'New customers only'},
];
const handleSync = async () => {
setLoading(true);
setSuccess(false);
setError(false);
try {
// Initiate sync through your app's backend
const response = await fetch('https://your-app.com/api/sync-customers', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
platform,
syncType,
}),
});
if (response.ok) {
setSuccess(true);
close();
} else {
setError(true);
}
} catch (err) {
setError(true);
} finally {
setLoading(false);
}
};
return (
{loading ? 'Starting Sync...' : 'Start Sync'}
}
secondaryAction={
}
>
{success && (
Customer sync initiated successfully! You'll receive an email when complete.
)}
{error && (
Failed to initiate sync. Please try again.
)}
This will sync all customers in the background. Large customer lists may take several minutes to complete.
);
}
```
##### TS
```ts
import {
extension,
AdminAction,
Banner,
Section,
BlockStack,
Select,
Button,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-index.action.render',
(root, api) => {
let loading = false;
let platform = 'mailchimp';
let syncType = 'full';
let success = false;
let error = false;
const platforms = [
{value: 'mailchimp', label: 'Mailchimp'},
{value: 'klaviyo', label: 'Klaviyo'},
{value: 'hubspot', label: 'HubSpot'},
{value: 'salesforce', label: 'Salesforce'},
];
const syncTypes = [
{value: 'full', label: 'Full sync (all customers)'},
{value: 'incremental', label: 'Incremental (changes only)'},
{value: 'new', label: 'New customers only'},
];
const handleSync = async () => {
loading = true;
success = false;
error = false;
updateUI();
try {
// Initiate sync through your app's backend
const response = await fetch('https://your-app.com/api/sync-customers', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
platform,
syncType,
}),
});
if (response.ok) {
success = true;
updateUI();
api.close();
} else {
error = true;
updateUI();
}
} catch (err) {
error = true;
updateUI();
} finally {
loading = false;
updateUI();
}
};
const primaryAction = root.createFragment();
const secondaryAction = root.createFragment();
const content = root.createFragment();
const updateUI = () => {
content.replaceChildren();
if (success) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'success', dismissible: true},
"Customer sync initiated successfully! You'll receive an email when complete."
)
);
}
if (error) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'critical', dismissible: true},
'Failed to initiate sync. Please try again.'
)
);
}
const section = root.createComponent(Section, {heading: 'Sync settings'});
const blockStack = root.createComponent(BlockStack);
blockStack.appendChild(
root.createComponent(Select, {
label: 'Platform',
value: platform,
onChange: (value) => {
platform = value;
},
options: platforms,
})
);
blockStack.appendChild(
root.createComponent(Select, {
label: 'Sync type',
value: syncType,
onChange: (value) => {
syncType = value;
},
options: syncTypes,
})
);
blockStack.appendChild(
root.createComponent(
Banner,
{tone: 'info'},
'This will sync all customers in the background. Large customer lists may take several minutes to complete.'
)
);
section.appendChild(blockStack);
content.appendChild(section);
};
primaryAction.appendChild(
root.createComponent(
Button,
{
onPress: handleSync,
disabled: loading || success,
},
loading ? 'Starting Sync...' : 'Start Sync'
)
);
secondaryAction.appendChild(
root.createComponent(Button, {onPress: () => api.close()}, 'Cancel')
);
updateUI();
const adminAction = root.createComponent(
AdminAction,
{
title: 'Sync Customers',
primaryAction,
secondaryAction,
}
);
adminAction.appendChild(content);
root.appendChild(adminAction);
root.mount();
}
);
```
### Customer index action (should render) target
`admin.customer-index.action.should-render`
Controls the render state of an admin action extension on the customer index page. Use this target to conditionally show or hide your action extension based on business logic, user permissions, or app configuration.
This target returns a boolean value that determines whether the corresponding action extension appears in the **More actions** menu. The extension is evaluated each time the page loads.
### Support Components (0) APIs (1)
### Supported components
\-
### Available APIs
* [Should Render API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/utility-apis/should-render-api)
Examples
### Examples
* ####
##### Description
Conditionally display an action only when the app is properly configured. This example demonstrates how to check configuration status before showing the extension.
##### React
```tsx
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-index.action.should-render',
async (root, api) => {
try {
// Check if app is configured through your backend
const response = await fetch(
'https://your-app.com/api/check-configuration'
);
const {configured} = await response.json();
// Only show action if app is configured
return {render: configured};
} catch (err) {
console.error('Error checking configuration:', err);
return {render: false};
}
}
);
```
##### TS
```ts
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-index.action.should-render',
async (root, api) => {
try {
// Check if app is configured through your backend
const response = await fetch(
'https://your-app.com/api/check-configuration'
);
const {configured} = await response.json();
// Only show action if app is configured
return {render: configured};
} catch (err) {
console.error('Error checking configuration:', err);
return {render: false};
}
}
);
```
* ####
##### Description
Conditionally display an action only when the merchant's subscription plan includes customer analytics features. This example demonstrates checking plan entitlements before showing the extension.
##### React
```tsx
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-index.action.should-render',
async (root, api) => {
try {
// Check plan features through your app's backend
const response = await fetch(
'https://your-app.com/api/check-plan-features'
);
const {features} = await response.json();
// Only show action if customer analytics feature is available
return {render: features.includes('customer-analytics')};
} catch (err) {
console.error('Error checking plan features:', err);
return {render: false};
}
}
);
```
##### TS
```ts
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-index.action.should-render',
async (root, api) => {
try {
// Check plan features through your app's backend
const response = await fetch(
'https://your-app.com/api/check-plan-features'
);
const {features} = await response.json();
// Only show action if customer analytics feature is available
return {render: features.includes('customer-analytics')};
} catch (err) {
console.error('Error checking plan features:', err);
return {render: false};
}
}
);
```
### Customer index selection action target
`admin.customer-index.selection-action.render`
Renders a selection action extension on the customer index page when multiple customers are selected. Merchants can access this extension from the **More actions** menu of the resource list. Use this target to provide bulk operations on selected customers, such as bulk export, bulk tagging, or bulk updates.
Extensions at this target can access the IDs of selected customers through the `data` property in the [Action Extension API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/core-apis/action-extension-api). The action renders in a modal overlay designed for batch processing.
### Support Components (35) APIs (1)
### Supported components
* [AdminAction](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminaction)
* [AdminBlock](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminblock)
* [AdminPrintAction](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminprintaction)
* [Badge](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/badge)
* [Banner](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/banner)
* [BlockStack](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/blockstack)
* [Box](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/box)
* [Button](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/button)
* [Checkbox](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/checkbox)
* [ChoiceList](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/choicelist)
* [ColorPicker](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/colorpicker)
* [DateField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/datefield)
* [DatePicker](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/datepicker)
* [Divider](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/divider)
* [EmailField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/emailfield)
* [Form](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/form)
* [FunctionSettings](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/functionsettings)
* [Heading](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/heading)
* [HeadingGroup](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/headinggroup)
* [Icon](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/media-and-visuals/icon)
* [Image](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/media-and-visuals/image)
* [InlineStack](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/inlinestack)
* [Link](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/link)
* [MoneyField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/moneyfield)
* [NumberField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/numberfield)
* [Paragraph](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/paragraph)
* [PasswordField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/passwordfield)
* [Pressable](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/pressable)
* [ProgressIndicator](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/progressindicator)
* [Section](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/section)
* [Select](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/select)
* [Text](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/text)
* [TextArea](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/textarea)
* [TextField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/textfield)
* [URLField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/urlfield)
### Available APIs
* [Action Extension API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/core-apis/action-extension-api)
Examples
### Examples
* ####
##### Description
Add a selection action extension that exports selected customers to a marketing platform. This example shows how to process multiple customer IDs and sync them with an external service.
##### React
```tsx
import React from 'react';
import {useState, useEffect} from 'react';
import {
reactExtension,
useApi,
AdminAction,
Banner,
Section,
BlockStack,
Select,
Text,
Button,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customer-index.selection-action.render';
export default reactExtension(TARGET, () => );
function App() {
const {data, close, query} = useApi(TARGET);
const [loading, setLoading] = useState(false);
const [customerCount, setCustomerCount] = useState(0);
const [listId, setListId] = useState('');
const [lists, setLists] = useState([]);
const [success, setSuccess] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
const selectedCustomers = data.selected || [];
setCustomerCount(selectedCustomers.length);
// Fetch available marketing lists
const fetchLists = async () => {
try {
const response = await fetch('https://your-app.com/api/marketing-lists');
const listsData = await response.json();
setLists(listsData.lists.map(list => ({
value: list.id,
label: `${list.name} (${list.subscriberCount} subscribers)`,
})));
if (listsData.lists.length > 0) {
setListId(listsData.lists[0].id);
}
} catch (err) {
console.error('Error fetching lists:', err);
}
};
fetchLists();
}, [data]);
const handleExport = async () => {
setLoading(true);
setSuccess(false);
setError(false);
const customerIds = data.selected.map((customer) => customer.id);
try {
// Fetch customer emails from GraphQL Admin API
const {data: customerData} = await query(
`
query GetCustomers($ids: [ID!]!) {
nodes(ids: $ids) {
... on Customer {
id
email
firstName
lastName
tags
}
}
}
`,
{variables: {ids: customerIds}}
);
// Export to marketing platform through your app's backend
const response = await fetch('https://your-app.com/api/export-to-marketing', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
customers: customerData.nodes,
listId,
}),
});
if (response.ok) {
setSuccess(true);
close();
} else {
setError(true);
}
} catch (err) {
setError(true);
} finally {
setLoading(false);
}
};
return (
{loading ? 'Exporting...' : 'Export Customers'}
}
secondaryAction={
}
>
{success && (
{customerCount} customers exported successfully!
)}
{error && (
Failed to export customers. Please try again.
)}
Exporting {customerCount} selected customer{customerCount !== 1 ? 's' : ''}
);
}
```
##### TS
```ts
import {
extension,
AdminAction,
Banner,
Section,
BlockStack,
Select,
Text,
Button,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-index.selection-action.render',
async (root, api) => {
const selectedCustomers = api.data.selected || [];
const customerCount = selectedCustomers.length;
let loading = false;
let listId = '';
let lists = [];
let success = false;
let error = false;
const adminAction = root.createComponent(AdminAction, {
title: 'Export to Marketing Platform',
});
root.appendChild(adminAction);
root.mount();
// Fetch available marketing lists
try {
const response = await fetch('https://your-app.com/api/marketing-lists');
const listsData = await response.json();
lists = listsData.lists.map(list => ({
value: list.id,
label: `${list.name} (${list.subscriberCount} subscribers)`,
}));
if (listsData.lists.length > 0) {
listId = listsData.lists[0].id;
}
} catch (err) {
console.error('Error fetching lists:', err);
}
const handleExport = async () => {
loading = true;
success = false;
error = false;
updateUI();
const customerIds = api.data.selected.map((customer) => customer.id);
try {
// Fetch customer emails from GraphQL Admin API
const {data: customerData} = await api.query(
`
query GetCustomers($ids: [ID!]!) {
nodes(ids: $ids) {
... on Customer {
id
email
firstName
lastName
tags
}
}
}
`,
{variables: {ids: customerIds}}
);
// Export to marketing platform through your app's backend
const response = await fetch('https://your-app.com/api/export-to-marketing', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
customers: customerData.nodes,
listId,
}),
});
if (response.ok) {
success = true;
updateUI();
api.close();
} else {
error = true;
updateUI();
}
} catch (err) {
error = true;
updateUI();
} finally {
loading = false;
updateUI();
}
};
const primaryAction = root.createFragment();
const secondaryAction = root.createFragment();
const content = root.createFragment();
const updateUI = () => {
content.replaceChildren();
primaryAction.replaceChildren();
secondaryAction.replaceChildren();
if (success) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'success', dismissible: true},
`${customerCount} customers exported successfully!`
)
);
}
if (error) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'critical', dismissible: true},
'Failed to export customers. Please try again.'
)
);
}
const section = root.createComponent(Section, {heading: 'Export settings'});
const blockStack = root.createComponent(BlockStack);
blockStack.appendChild(
root.createComponent(
Text,
{},
`Exporting ${customerCount} selected customer${customerCount !== 1 ? 's' : ''}`
)
);
blockStack.appendChild(
root.createComponent(Select, {
label: 'Marketing list',
value: listId,
onChange: (value) => {
listId = value;
},
options: lists,
})
);
section.appendChild(blockStack);
content.appendChild(section);
primaryAction.appendChild(
root.createComponent(
Button,
{
onPress: handleExport,
disabled: loading || success || !listId,
},
loading ? 'Exporting...' : 'Export Customers'
)
);
secondaryAction.appendChild(
root.createComponent(Button, {onPress: () => api.close()}, 'Cancel')
);
};
updateUI();
adminAction.setProps({
title: 'Export to Marketing Platform',
primaryAction,
secondaryAction,
});
adminAction.appendChild(content);
}
);
```
* ####
##### Description
Add a selection action extension that applies tags to multiple customers at once. This example demonstrates bulk operations using the Admin GraphQL API.
##### React
```tsx
import React from 'react';
import {useState} from 'react';
import {
reactExtension,
useApi,
AdminAction,
Banner,
Section,
BlockStack,
TextField,
Text,
Button,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customer-index.selection-action.render';
export default reactExtension(TARGET, () => );
function App() {
const {data, close} = useApi(TARGET);
const [loading, setLoading] = useState(false);
const [tags, setTags] = useState('');
const [success, setSuccess] = useState(false);
const [error, setError] = useState(false);
const customerCount = data.selected?.length || 0;
const handleTag = async () => {
setLoading(true);
setSuccess(false);
setError(false);
const customerIds = data.selected.map((customer) => customer.id);
const tagArray = tags.split(',').map((tag) => tag.trim()).filter(Boolean);
try {
// Apply tags through your app's backend
const response = await fetch('https://your-app.com/api/bulk-tag', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
customerIds,
tags: tagArray,
}),
});
if (response.ok) {
setSuccess(true);
close();
} else {
setError(true);
}
} catch (err) {
setError(true);
} finally {
setLoading(false);
}
};
return (
{loading ? 'Applying Tags...' : 'Apply Tags'}
}
secondaryAction={
}
>
{success && (
Tags applied to {customerCount} customers!
)}
{error && (
Failed to apply tags. Please try again.
)}
Apply tags to {customerCount} selected customer{customerCount !== 1 ? 's' : ''}
Enter one or more tags separated by commas
);
}
```
##### TS
```ts
import {
extension,
AdminAction,
Banner,
Section,
BlockStack,
TextField,
Text,
Button,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-index.selection-action.render',
(root, api) => {
let loading = false;
let tags = '';
let success = false;
let error = false;
const customerCount = api.data.selected?.length || 0;
const handleTag = async () => {
loading = true;
success = false;
error = false;
updateUI();
const customerIds = api.data.selected.map((customer) => customer.id);
const tagArray = tags.split(',').map((tag) => tag.trim()).filter(Boolean);
try {
// Apply tags through your app's backend
const response = await fetch('https://your-app.com/api/bulk-tag', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
customerIds,
tags: tagArray,
}),
});
if (response.ok) {
success = true;
updateUI();
api.close();
} else {
error = true;
updateUI();
}
} catch (err) {
error = true;
updateUI();
} finally {
loading = false;
updateUI();
}
};
const primaryAction = root.createFragment();
const secondaryAction = root.createFragment();
const content = root.createFragment();
const updateUI = () => {
content.replaceChildren();
primaryAction.replaceChildren();
secondaryAction.replaceChildren();
if (success) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'success', dismissible: true},
`Tags applied to ${customerCount} customers!`
)
);
}
if (error) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'critical', dismissible: true},
'Failed to apply tags. Please try again.'
)
);
}
const section = root.createComponent(Section, {heading: 'Tag settings'});
const blockStack = root.createComponent(BlockStack);
blockStack.appendChild(
root.createComponent(
Text,
{},
`Apply tags to ${customerCount} selected customer${customerCount !== 1 ? 's' : ''}`
)
);
blockStack.appendChild(
root.createComponent(TextField, {
label: 'Tags (comma-separated)',
value: tags,
onChange: (value) => {
tags = value;
},
placeholder: 'VIP, Newsletter, Holiday2024',
})
);
blockStack.appendChild(
root.createComponent(
Text,
{tone: 'subdued'},
'Enter one or more tags separated by commas'
)
);
section.appendChild(blockStack);
content.appendChild(section);
primaryAction.appendChild(
root.createComponent(
Button,
{
onPress: handleTag,
disabled: loading || success || !tags.trim(),
},
loading ? 'Applying Tags...' : 'Apply Tags'
)
);
secondaryAction.appendChild(
root.createComponent(Button, {onPress: () => api.close()}, 'Cancel')
);
};
updateUI();
const adminAction = root.createComponent(
AdminAction,
{
title: 'Bulk Tag Customers',
primaryAction,
secondaryAction,
}
);
adminAction.appendChild(content);
root.appendChild(adminAction);
root.mount();
}
);
```
### Customer index selection action (should render) target
`admin.customer-index.selection-action.should-render`
Controls the render state of a selection action extension on the customer index page when multiple customers are selected. Use this target to conditionally show or hide your bulk action extension based on the number of selected customers, their properties, or app configuration.
This target returns a boolean value that determines whether the corresponding selection action extension appears in the **More actions** menu. The extension is evaluated each time the selection changes.
### Support Components (0) APIs (1)
### Supported components
\-
### Available APIs
* [Should Render API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/utility-apis/should-render-api)
Examples
### Examples
* ####
##### Description
Conditionally display a bulk action only when a reasonable number of customers are selected. This example demonstrates how to limit bulk operations based on selection size.
##### React
```tsx
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-index.selection-action.should-render',
async (root, api) => {
const selectedCount = api.data.selected?.length || 0;
// Only show action if between 1 and 100 customers are selected
return {render: selectedCount > 0 && selectedCount <= 100};
}
);
```
##### TS
```ts
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-index.selection-action.should-render',
async (root, api) => {
const selectedCount = api.data.selected?.length || 0;
// Only show action if between 1 and 100 customers are selected
return {render: selectedCount > 0 && selectedCount <= 100};
}
);
```
* ####
##### Description
Conditionally display a bulk action based on whether selected customers meet specific criteria. This example demonstrates checking customer properties before showing the extension.
##### React
```tsx
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-index.selection-action.should-render',
async (root, api) => {
const selectedIds = api.data.selected?.map((customer) => customer.id) || [];
const selectedCount = selectedIds.length;
// Don't show for empty selections
if (selectedCount === 0) {
return {render: false};
}
try {
// Check if selected customers are eligible for the action
const response = await fetch(
'https://your-app.com/api/check-eligible-customers',
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({customerIds: selectedIds}),
}
);
const {eligibleCount} = await response.json();
// Only show action if at least one selected customer is eligible
return {render: eligibleCount > 0};
} catch (err) {
console.error('Error checking eligibility:', err);
return {render: false};
}
}
);
```
##### TS
```ts
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-index.selection-action.should-render',
async (root, api) => {
const selectedIds = api.data.selected?.map((customer) => customer.id) || [];
const selectedCount = selectedIds.length;
// Don't show for empty selections
if (selectedCount === 0) {
return {render: false};
}
try {
// Check if selected customers are eligible for the action
const response = await fetch(
'https://your-app.com/api/check-eligible-customers',
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({customerIds: selectedIds}),
}
);
const {eligibleCount} = await response.json();
// Only show action if at least one selected customer is eligible
return {render: eligibleCount > 0};
} catch (err) {
console.error('Error checking eligibility:', err);
return {render: false};
}
}
);
```
***
## Customer segment targets
Use [action and runnable targets](https://shopify.dev/docs/api/admin-extensions/2025-07#building-your-extension) to extend customer segment pages with workflows that help merchants use and export their customer segments for marketing and analysis. Action targets render UI workflows for segment operations, while runnable targets return data to populate pre-built customer segment templates.
### Customer segment details action target
`admin.customer-segment-details.action.render`
Renders an admin action extension on the customer segment details page. Merchants can access this extension from the **Use segment** button. Use this target to provide workflows that operate on customer segments, such as exporting segments to marketing platforms, creating targeted campaigns, or syncing with external analytics tools.
Extensions at this target can access information about the segment through the `data` property in the [Action Extension API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/core-apis/action-extension-api). The action renders in a modal overlay, providing space for segment-specific workflows.
### Support Components (35) APIs (1)
### Supported components
* [AdminAction](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminaction)
* [AdminBlock](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminblock)
* [AdminPrintAction](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminprintaction)
* [Badge](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/badge)
* [Banner](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/banner)
* [BlockStack](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/blockstack)
* [Box](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/box)
* [Button](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/button)
* [Checkbox](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/checkbox)
* [ChoiceList](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/choicelist)
* [ColorPicker](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/colorpicker)
* [DateField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/datefield)
* [DatePicker](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/datepicker)
* [Divider](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/divider)
* [EmailField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/emailfield)
* [Form](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/form)
* [FunctionSettings](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/functionsettings)
* [Heading](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/heading)
* [HeadingGroup](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/headinggroup)
* [Icon](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/media-and-visuals/icon)
* [Image](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/media-and-visuals/image)
* [InlineStack](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/inlinestack)
* [Link](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/link)
* [MoneyField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/moneyfield)
* [NumberField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/numberfield)
* [Paragraph](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/paragraph)
* [PasswordField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/passwordfield)
* [Pressable](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/actions/pressable)
* [ProgressIndicator](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/feedback-and-status-indicators/progressindicator)
* [Section](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/layout-and-structure/section)
* [Select](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/select)
* [Text](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/typography-and-content/text)
* [TextArea](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/textarea)
* [TextField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/textfield)
* [URLField](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/urlfield)
### Available APIs
* [Action Extension API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/core-apis/action-extension-api)
Examples
### Examples
* ####
##### Description
Add an action extension that exports a customer segment to an email marketing platform. This example shows how to fetch segment data and create a synchronized audience in an external service.
##### React
```tsx
import React from 'react';
import {useState, useEffect} from 'react';
import {
reactExtension,
useApi,
AdminAction,
Banner,
Section,
BlockStack,
Box,
Heading,
TextField,
Text,
Button,
ProgressIndicator,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customer-segment-details.action.render';
export default reactExtension(TARGET, () => );
function App() {
const {data, close, query} = useApi(TARGET);
const [loading, setLoading] = useState(false);
const [fetching, setFetching] = useState(true);
const [segmentName, setSegmentName] = useState('');
const [customerCount, setCustomerCount] = useState(0);
const [audienceName, setAudienceName] = useState('');
const [success, setSuccess] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
const fetchSegmentDetails = async () => {
const segmentId = data.selected[0].id;
try {
// Fetch segment details from GraphQL Admin API
const {data: segmentData} = await query(
`
query GetSegment($id: ID!) {
segment(id: $id) {
name
query
}
}
`,
{variables: {id: segmentId}}
);
setSegmentName(segmentData.segment.name);
setAudienceName(segmentData.segment.name);
// Get customer count from your app's backend
const countResponse = await fetch(
`https://your-app.com/api/segment-count?segmentId=${segmentId}`
);
const countData = await countResponse.json();
setCustomerCount(countData.count);
} catch (err) {
console.error('Error fetching segment:', err);
} finally {
setFetching(false);
}
};
fetchSegmentDetails();
}, [data, query]);
const handleExport = async () => {
setLoading(true);
setSuccess(false);
setError(false);
const segmentId = data.selected[0].id;
try {
// Export segment through your app's backend
const response = await fetch('https://your-app.com/api/export-segment', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
segmentId,
audienceName,
}),
});
if (response.ok) {
setSuccess(true);
close();
} else {
setError(true);
}
} catch (err) {
setError(true);
} finally {
setLoading(false);
}
};
if (fetching) {
return (
Loading segment details...
);
}
return (
{loading ? 'Exporting...' : 'Create Audience'}
}
secondaryAction={
}
>
{success && (
Segment exported successfully! Audience "{audienceName}" created.
)}
{error && (
Failed to export segment. Please try again.
)}
Source segment
{segmentName}
Customer count
{customerCount.toLocaleString()} customers
);
}
```
##### TS
```ts
import {
extension,
AdminAction,
Banner,
Section,
BlockStack,
Box,
Heading,
TextField,
Text,
Button,
ProgressIndicator,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-segment-details.action.render',
async (root, api) => {
const segmentId = api.data.selected[0].id;
let loading = false;
let segmentName = '';
let customerCount = 0;
let audienceName = '';
let success = false;
let error = false;
const adminAction = root.createComponent(AdminAction, {title: 'Export to Email Platform'});
// Show loading state
const loadingStack = root.createComponent(BlockStack);
loadingStack.appendChild(root.createComponent(ProgressIndicator, {size: 'small-100'}));
loadingStack.appendChild(root.createComponent(Text, {}, 'Loading segment details...'));
adminAction.appendChild(loadingStack);
root.appendChild(adminAction);
root.mount();
try {
// Fetch segment details from GraphQL Admin API
const {data: segmentData} = await api.query(
`
query GetSegment($id: ID!) {
segment(id: $id) {
name
query
}
}
`,
{variables: {id: segmentId}}
);
segmentName = segmentData.segment.name;
audienceName = segmentData.segment.name;
// Get customer count from your app's backend
const countResponse = await fetch(
`https://your-app.com/api/segment-count?segmentId=${segmentId}`
);
const countData = await countResponse.json();
customerCount = countData.count;
} catch (err) {
console.error('Error fetching segment:', err);
}
const handleExport = async () => {
loading = true;
success = false;
error = false;
updateUI();
try {
// Export segment through your app's backend
const response = await fetch('https://your-app.com/api/export-segment', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
segmentId,
audienceName,
}),
});
if (response.ok) {
success = true;
updateUI();
api.close();
} else {
error = true;
updateUI();
}
} catch (err) {
error = true;
updateUI();
} finally {
loading = false;
updateUI();
}
};
const primaryAction = root.createFragment();
const secondaryAction = root.createFragment();
const content = root.createFragment();
const updateUI = () => {
content.replaceChildren();
primaryAction.replaceChildren();
secondaryAction.replaceChildren();
if (success) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'success', dismissible: true},
`Segment exported successfully! Audience "${audienceName}" created.`
)
);
}
if (error) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'critical', dismissible: true},
'Failed to export segment. Please try again.'
)
);
}
const section = root.createComponent(Section, {heading: 'Segment information'});
const blockStack = root.createComponent(BlockStack);
const sourceBox = root.createComponent(Box);
sourceBox.appendChild(root.createComponent(Heading, {}, 'Source segment'));
sourceBox.appendChild(root.createComponent(Text, {}, segmentName));
blockStack.appendChild(sourceBox);
const countBox = root.createComponent(Box);
countBox.appendChild(root.createComponent(Heading, {}, 'Customer count'));
countBox.appendChild(
root.createComponent(Text, {}, `${customerCount.toLocaleString()} customers`)
);
blockStack.appendChild(countBox);
blockStack.appendChild(
root.createComponent(TextField, {
label: 'Audience name',
value: audienceName,
onChange: (value) => {
audienceName = value;
},
})
);
section.appendChild(blockStack);
content.appendChild(section);
primaryAction.appendChild(
root.createComponent(
Button,
{
onPress: handleExport,
disabled: loading || success || !audienceName.trim(),
},
loading ? 'Exporting...' : 'Create Audience'
)
);
secondaryAction.appendChild(
root.createComponent(Button, {onPress: () => api.close()}, 'Cancel')
);
};
// Clear loading state and show form
adminAction.replaceChildren();
updateUI();
adminAction.setProps({
title: 'Export to Email Platform',
primaryAction,
secondaryAction,
});
adminAction.appendChild(content);
}
);
```
* ####
##### Description
Add an action extension that creates a targeted advertising campaign based on a customer segment. This example demonstrates how to integrate segment data with advertising platforms.
##### React
```tsx
import React from 'react';
import {useState} from 'react';
import {
reactExtension,
useApi,
AdminAction,
Banner,
Section,
BlockStack,
TextField,
Select,
Button,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customer-segment-details.action.render';
export default reactExtension(TARGET, () => );
function App() {
const {data, close} = useApi(TARGET);
const [loading, setLoading] = useState(false);
const [campaignName, setCampaignName] = useState('');
const [budget, setBudget] = useState('100');
const [duration, setDuration] = useState('7days');
const [success, setSuccess] = useState(false);
const [error, setError] = useState(false);
const durations = [
{value: '7days', label: '7 days'},
{value: '14days', label: '14 days'},
{value: '30days', label: '30 days'},
{value: 'ongoing', label: 'Ongoing'},
];
const handleCreate = async () => {
setLoading(true);
setSuccess(false);
setError(false);
const segmentId = data.selected[0].id;
try {
// Create campaign through your app's backend
const response = await fetch('https://your-app.com/api/create-campaign', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
segmentId,
campaignName,
budget: parseFloat(budget),
duration,
}),
});
if (response.ok) {
setSuccess(true);
close();
} else {
setError(true);
}
} catch (err) {
setError(true);
} finally {
setLoading(false);
}
};
return (
{loading ? 'Creating...' : 'Create Campaign'}
}
secondaryAction={
}
>
{success && (
Campaign created successfully!
)}
{error && (
Failed to create campaign. Please try again.
)}
);
}
```
##### TS
```ts
import {
extension,
AdminAction,
Banner,
Section,
BlockStack,
TextField,
Select,
Button,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-segment-details.action.render',
(root, api) => {
let loading = false;
let campaignName = '';
let budget = '100';
let duration = '7days';
let success = false;
let error = false;
const durations = [
{value: '7days', label: '7 days'},
{value: '14days', label: '14 days'},
{value: '30days', label: '30 days'},
{value: 'ongoing', label: 'Ongoing'},
];
const handleCreate = async () => {
loading = true;
success = false;
error = false;
updateUI();
const segmentId = api.data.selected[0].id;
try {
// Create campaign through your app's backend
const response = await fetch('https://your-app.com/api/create-campaign', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
segmentId,
campaignName,
budget: parseFloat(budget),
duration,
}),
});
if (response.ok) {
success = true;
updateUI();
api.close();
} else {
error = true;
updateUI();
}
} catch (err) {
error = true;
updateUI();
} finally {
loading = false;
updateUI();
}
};
const primaryAction = root.createFragment();
const secondaryAction = root.createFragment();
const content = root.createFragment();
const updateUI = () => {
content.replaceChildren();
primaryAction.replaceChildren();
secondaryAction.replaceChildren();
if (success) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'success', dismissible: true},
'Campaign created successfully!'
)
);
}
if (error) {
content.appendChild(
root.createComponent(
Banner,
{tone: 'critical', dismissible: true},
'Failed to create campaign. Please try again.'
)
);
}
const section = root.createComponent(Section, {heading: 'Campaign settings'});
const blockStack = root.createComponent(BlockStack);
blockStack.appendChild(
root.createComponent(TextField, {
label: 'Campaign name',
value: campaignName,
onChange: (value) => {
campaignName = value;
},
placeholder: 'Holiday promotion for high-value customers',
})
);
blockStack.appendChild(
root.createComponent(TextField, {
label: 'Daily budget ($)',
type: 'number',
value: budget,
onChange: (value) => {
budget = value;
},
})
);
blockStack.appendChild(
root.createComponent(Select, {
label: 'Campaign duration',
value: duration,
onChange: (value) => {
duration = value;
},
options: durations,
})
);
section.appendChild(blockStack);
content.appendChild(section);
primaryAction.appendChild(
root.createComponent(
Button,
{
onPress: handleCreate,
disabled: loading || success || !campaignName.trim(),
},
loading ? 'Creating...' : 'Create Campaign'
)
);
secondaryAction.appendChild(
root.createComponent(Button, {onPress: () => api.close()}, 'Cancel')
);
};
updateUI();
const adminAction = root.createComponent(
AdminAction,
{
title: 'Create Ad Campaign',
primaryAction,
secondaryAction,
}
);
adminAction.appendChild(content);
root.appendChild(adminAction);
root.mount();
}
);
```
### Customer segment details action (should render) target
`admin.customer-segment-details.action.should-render`
Controls the render state of an admin action extension on the customer segment details page. Use this target to conditionally show or hide your action extension based on the segment's properties, such as customer count, segment query complexity, or app configuration.
This target returns a boolean value that determines whether the corresponding action extension appears in the **Use segment** button menu. The extension is evaluated each time the page loads.
### Support Components (0) APIs (1)
### Supported components
\-
### Available APIs
* [Should Render API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/utility-apis/should-render-api)
Examples
### Examples
* ####
##### Description
Conditionally display an action only for segments that contain customers. This example demonstrates how to check segment size before showing the extension.
##### React
```tsx
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-segment-details.action.should-render',
async (root, api) => {
const segmentId = api.data.selected[0].id;
try {
// Check segment size through your app's backend
const response = await fetch(
`https://your-app.com/api/segment-count?segmentId=${segmentId}`
);
const {count} = await response.json();
// Only show action if segment has at least 10 customers
return {render: count >= 10};
} catch (err) {
console.error('Error checking segment size:', err);
return {render: false};
}
}
);
```
##### TS
```ts
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-segment-details.action.should-render',
async (root, api) => {
const segmentId = api.data.selected[0].id;
try {
// Check segment size through your app's backend
const response = await fetch(
`https://your-app.com/api/segment-count?segmentId=${segmentId}`
);
const {count} = await response.json();
// Only show action if segment has at least 10 customers
return {render: count >= 10};
} catch (err) {
console.error('Error checking segment size:', err);
return {render: false};
}
}
);
```
* ####
##### Description
Conditionally display an action only for segments with valid query syntax. This example demonstrates validating segment queries through your app's backend before showing workflow actions.
##### React
```tsx
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-segment-details.action.should-render',
async (root, api) => {
const segmentId = api.data.selected[0].id;
try {
// Fetch segment query from GraphQL Admin API
const {data} = await api.query(
`
query GetSegment($id: ID!) {
segment(id: $id) {
query
}
}
`,
{variables: {id: segmentId}}
);
// Validate segment query through your app's backend
const validationResponse = await fetch(
'https://your-app.com/api/validate-segment-query',
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({query: data.segment.query}),
}
);
const {isValid} = await validationResponse.json();
// Only show action if segment query is valid
return {render: isValid};
} catch (err) {
console.error('Error validating segment:', err);
return {render: false};
}
}
);
```
##### TS
```ts
import {extension} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customer-segment-details.action.should-render',
async (root, api) => {
const segmentId = api.data.selected[0].id;
try {
// Fetch segment query from GraphQL Admin API
const {data} = await api.query(
`
query GetSegment($id: ID!) {
segment(id: $id) {
query
}
}
`,
{variables: {id: segmentId}}
);
// Validate segment query through your app's backend
const validationResponse = await fetch(
'https://your-app.com/api/validate-segment-query',
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({query: data.segment.query}),
}
);
const {isValid} = await validationResponse.json();
// Only show action if segment query is valid
return {render: isValid};
} catch (err) {
console.error('Error validating segment:', err);
return {render: false};
}
}
);
```
### Customer segmentation templates target
`admin.customers.segmentation-templates.render`
Use this target to provide a [customer segments template extension](https://shopify.dev/docs/apps/build/marketing-analytics/customer-segments/build-a-template-extension) that returns an array of templates in the customer segment editor. The templates include a title, description, query, and optional dependencies.
This target provides merchants with pre-built customer segment queries for common use cases like identifying VIP customers, finding at-risk customers, or creating cohorts based on purchase behavior.
Extensions at this target use the [Customer Segment Template Extension API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/contextual-apis/customer-segment-template-extension-api) to return template data. Templates appear in the segment editor's template gallery and can be inserted with a single click.
### Support Components (1) APIs (1)
### Supported components
* [CustomerSegmentTemplate](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/customersegmenttemplate)
### Available APIs
* CustomerSegmentTemplateApi
Examples
### Examples
* ####
##### Description
Provide customer segment templates that help merchants identify and target their most valuable customers. This example shows how to create templates for high-value customer segments.
##### React
```tsx
import {
reactExtension,
CustomerSegmentTemplate,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customers.segmentation-templates.render';
export default reactExtension(TARGET, () => );
function App() {
return (
<>
>
);
}
```
##### TS
```ts
import {
extension,
CustomerSegmentTemplate,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customers.segmentation-templates.render',
(root, api) => {
const template1 = root.createComponent(CustomerSegmentTemplate, {
title: 'High Lifetime Value Customers',
description: 'Identify your most valuable customers based on total spending. Perfect for VIP programs and exclusive promotions.',
query: 'amount_spent > 1000',
});
const template2 = root.createComponent(CustomerSegmentTemplate, {
title: 'Frequent Buyers',
description: 'Find customers who have purchased multiple times and are likely to buy again.',
query: 'number_of_orders >= 5 AND last_order_date >= -90 days',
});
const template3 = root.createComponent(CustomerSegmentTemplate, {
title: 'Recent High-Value Customers',
description: 'Target high-value customers who have purchased recently. Perfect for loyalty programs and exclusive offers.',
query: 'amount_spent > 500 AND last_order_date >= -30 days',
});
root.appendChild(template1);
root.appendChild(template2);
root.appendChild(template3);
root.mount();
}
);
```
* ####
##### Description
Provide customer segment templates that help merchants identify customers at risk of churning and opportunities for re-engagement. This example demonstrates templates focused on customer retention.
##### React
```tsx
import {
reactExtension,
CustomerSegmentTemplate,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customers.segmentation-templates.render';
export default reactExtension(TARGET, () => );
function App() {
return (
<>
>
);
}
```
##### TS
```ts
import {
extension,
CustomerSegmentTemplate,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customers.segmentation-templates.render',
(root, api) => {
const template1 = root.createComponent(CustomerSegmentTemplate, {
title: 'At-Risk Customers',
description: 'Find customers who previously purchased but haven\'t ordered recently. Great for win-back campaigns and re-engagement emails.',
query: 'number_of_orders >= 2 AND last_order_date <= -180 days',
});
const template2 = root.createComponent(CustomerSegmentTemplate, {
title: 'One-Time Buyers',
description: 'Identify customers who made only one purchase and encourage repeat orders.',
query: 'number_of_orders = 1 AND last_order_date >= -90 days',
});
const template3 = root.createComponent(CustomerSegmentTemplate, {
title: 'Lapsed High-Value Customers',
description: 'Target valuable customers who haven\'t purchased in a while. Combine high spending with recent inactivity.',
query: 'amount_spent > 500 AND last_order_date <= -120 days',
});
const template4 = root.createComponent(CustomerSegmentTemplate, {
title: 'Email Subscribers Without Orders',
description: 'Find customers who subscribed to marketing but never purchased.',
query: 'email_marketing_consent.state = SUBSCRIBED AND number_of_orders = 0',
});
root.appendChild(template1);
root.appendChild(template2);
root.appendChild(template3);
root.appendChild(template4);
root.mount();
}
);
```
* ####
##### Description
Provide customer segment templates that help merchants create cohorts based on customer acquisition and behavior patterns. This example shows templates for cohort analysis.
##### React
```tsx
import {
reactExtension,
CustomerSegmentTemplate,
} from '@shopify/ui-extensions-react/admin';
const TARGET = 'admin.customers.segmentation-templates.render';
export default reactExtension(TARGET, () => );
function App() {
return (
<>
>
);
}
```
##### TS
```ts
import {
extension,
CustomerSegmentTemplate,
} from '@shopify/ui-extensions/admin';
export default extension(
'admin.customers.segmentation-templates.render',
(root, api) => {
const template1 = root.createComponent(CustomerSegmentTemplate, {
title: 'New Customers This Month',
description: 'Target customers who made their first purchase within the last 30 days.',
query: 'first_order_date >= -30 days',
});
const template2 = root.createComponent(CustomerSegmentTemplate, {
title: 'Recent Holiday Shoppers',
description: 'Find customers who purchased in the last quarter of the year. Perfect for planning seasonal campaigns.',
query: 'last_order_date >= -120 days',
});
const template3 = root.createComponent(CustomerSegmentTemplate, {
title: 'Active Newsletter Subscribers',
description: 'Identify customers who accept email marketing and have purchased recently.',
query: 'email_marketing_consent.state = SUBSCRIBED AND last_order_date >= -60 days',
});
const template4 = root.createComponent(CustomerSegmentTemplate, {
title: 'Engaged Recent Customers',
description: 'Target customers who joined recently and are actively purchasing. Great for onboarding campaigns and early engagement.',
query: 'first_order_date >= -60 days AND number_of_orders >= 2',
});
root.appendChild(template1);
root.appendChild(template2);
root.appendChild(template3);
root.appendChild(template4);
root.mount();
}
);
```
***
## Best practices
* **Respect customer privacy:** Customer data is sensitive. Always handle customer information securely, comply with privacy regulations (GDPR, CCPA), and only request the data your extension truly needs to function.
* **Handle email consent properly:** When exporting customers for marketing purposes, always respect [email marketing consent](https://shopify.dev/docs/api/admin-graphql/latest/objects/Customer#field-Customer.fields.emailMarketingConsent) status and provide merchants with information about consent requirements to maintain compliance.
* **Support segmentation use cases:** When creating [customer segment templates](https://shopify.dev/docs/apps/build/marketing-analytics/customer-segments/build-a-template-extension), focus on actionable segments that help merchants make business decisions. Include clear descriptions explaining when and why to use each template.
***
## Limitations
* **Single target per module:** Each `[[extensions.targeting]]` entry in your [TOML configuration](https://shopify.dev/docs/api/admin-extensions/2025-07#configuration) maps one target to one module file.
* **Segment template query validation:** Customer segment template queries aren't validated at deployment. Invalid queries fail silently when merchants use the template. Test your template queries in the [segment editor](https://shopify.dev/docs/apps/build/marketing-analytics/customer-segments/build-a-template-extension) before deploying them to ensure they work correctly and return expected results.
* **Segment action location:** The `admin.customer-segment-details.action.render` target appears under the **Use segment** button, not in **More actions**.
* **Block target visibility:** Block extensions must be manually [added and pinned](https://help.shopify.com/manual/apps/working-with-apps#add-app-blocks-to-your-shopify-admin) by merchants before they appear.
* **Block collapse behavior:** Returning `null` from a block extension collapses the block rather than removing it from the page. Blocks can't be fully hidden at runtime.
***