Version 2025-07 is the last API version to support React-based UI components. Later versions use web components, native UI elements with built-in accessibility, better performance, and consistent styling with Shopify's design system. Check out the upgrade guide to upgrade your extension.
Customers
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.
Anchor to Use casesUse 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.

Anchor to Customer details targetsCustomer details targets
Use action and block targets 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 or your app's backend.
Anchor to Customer details action ,[object Object]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. The action renders in a modal overlay, providing space for multi-step workflows, forms, and confirmations.
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
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
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, () => <App />); 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 ( <AdminAction title="Export to CRM" primaryAction={ <Button onPress={handleExport} disabled={loading || success} > {loading ? 'Exporting...' : 'Export Customer'} </Button> } secondaryAction={ <Button onPress={close}> Cancel </Button> } > {success && ( <Banner tone="success" dismissible> Customer exported successfully! </Banner> )} {error && ( <Banner tone="critical" dismissible> Failed to export customer. Please try again. </Banner> )} <Section heading="Export options"> <BlockStack> <Checkbox checked={syncOrders} onChange={setSyncOrders} > Include order history </Checkbox> <Checkbox checked={syncMetafields} onChange={setSyncMetafields} > Include metafield data </Checkbox> </BlockStack> </Section> </AdminAction> ); }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
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, () => <App />); 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 ( <AdminAction title="Update Loyalty Status"> <BlockStack> <ProgressIndicator size="small-100" /> <Text>Loading loyalty information...</Text> </BlockStack> </AdminAction> ); } return ( <AdminAction title="Update Loyalty Status" primaryAction={ <Button onPress={handleUpdate} disabled={loading || success || newTier === currentTier} > {loading ? 'Updating...' : 'Update Tier'} </Button> } secondaryAction={ <Button onPress={close}> Cancel </Button> } > {success && ( <Banner tone="success" dismissible> Loyalty status updated successfully! </Banner> )} {error && ( <Banner tone="critical" dismissible> Failed to update loyalty status. Please try again. </Banner> )} <Section heading="Loyalty tier"> <BlockStack> <Box> <Text fontWeight="bold">Current tier: </Text> <Text>{currentTier}</Text> </Box> <Select label="New tier" value={newTier} onChange={setNewTier} options={tiers} /> </BlockStack> </Section> </AdminAction> ); }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); } );
Anchor to Customer details action (should render) ,[object Object]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.
Supported components
Available APIs
Supported components
Available APIs
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
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
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
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
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}; } } );
Anchor to Customer details block ,[object Object]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.
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
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
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, () => <App />); 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 ( <AdminBlock title="Loyalty Status"> <BlockStack> <ProgressIndicator size="small-100" /> <Text>Loading loyalty information...</Text> </BlockStack> </AdminBlock> ); } if (!loyaltyData) { return ( <AdminBlock title="Loyalty Status"> <Text>Unable to load loyalty information</Text> </AdminBlock> ); } return ( <AdminBlock title="Loyalty Status"> <BlockStack> <Box> <Badge tone={loyaltyData.tier === 'Platinum' ? 'success' : 'info'}> {loyaltyData.tier} Tier </Badge> </Box> <Divider /> <Box> <Heading>Points Balance</Heading> <InlineStack blockAlignment="center"> <Text fontWeight="bold">{loyaltyData.points.toLocaleString()}</Text> <Text tone="subdued">points</Text> </InlineStack> </Box> <Divider /> <Box> <Heading>Lifetime Value</Heading> <Text>${loyaltyData.lifetimeValue.toLocaleString()}</Text> </Box> <Divider /> <Box> <Heading>Member Since</Heading> <Text tone="subdued">{loyaltyData.memberSince}</Text> </Box> {loyaltyData.nextTier && ( <> <Divider /> <Box> <Heading>Progress to {loyaltyData.nextTier}</Heading> <Text tone="subdued"> Spend ${loyaltyData.nextTierRequired} more to unlock </Text> </Box> </> )} </BlockStack> </AdminBlock> ); }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
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, () => <App />); 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 ( <AdminBlock title="CRM Information"> <ProgressIndicator size="small-100" /> </AdminBlock> ); } if (!crmData) { return ( <AdminBlock title="CRM Information"> <Text>No CRM data available</Text> </AdminBlock> ); } return ( <AdminBlock title="CRM Information"> <BlockStack> <Box> <Heading>Account Status</Heading> <Badge tone={crmData.status === 'Active' ? 'success' : 'warning'}> {crmData.status} </Badge> </Box> <Divider /> <Box> <Heading>Last Contact</Heading> <Text>{crmData.lastContact}</Text> <Text tone="subdued">via {crmData.contactMethod}</Text> </Box> <Divider /> <Box> <Heading>Assigned Sales Rep</Heading> <Text>{crmData.salesRep}</Text> </Box> <Divider /> <Box> <Heading>Open Tickets</Heading> <Text>{crmData.openTickets} active support tickets</Text> </Box> {crmData.notes && ( <> <Divider /> <Box> <Heading>Recent Notes</Heading> <Text tone="subdued">{crmData.notes}</Text> </Box> </> )} <Button onPress={() => window.open(crmData.crmUrl, '_blank')} variant="secondary" > View in CRM </Button> </BlockStack> </AdminBlock> ); }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')); } } );
Anchor to Customer index targetsCustomer index targets
Use action targets to extend the customer index page with bulk operations and workflows that help merchants manage multiple customers efficiently.
Anchor to Customer index action ,[object Object]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. The action renders in a modal overlay, providing space for configuration and execution of list-wide operations.
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
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
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, () => <App />); 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 ( <AdminAction title="Generate Customer Report" primaryAction={ <Button onPress={handleGenerate} disabled={loading || success} > {loading ? 'Generating...' : 'Generate Report'} </Button> } secondaryAction={ <Button onPress={close}> Cancel </Button> } > {success && ( <Banner tone="success" dismissible> Report generated successfully! </Banner> )} {error && ( <Banner tone="critical" dismissible> Failed to generate report. Please try again. </Banner> )} <Section heading="Report configuration"> <BlockStack> <Select label="Report type" value={reportType} onChange={setReportType} options={reportTypes} /> <Select label="Date range" value={dateRange} onChange={setDateRange} options={dateRanges} /> </BlockStack> </Section> </AdminAction> ); }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
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, () => <App />); 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 ( <AdminAction title="Sync Customers" primaryAction={ <Button onPress={handleSync} disabled={loading || success} > {loading ? 'Starting Sync...' : 'Start Sync'} </Button> } secondaryAction={ <Button onPress={close}> Cancel </Button> } > {success && ( <Banner tone="success" dismissible> Customer sync initiated successfully! You'll receive an email when complete. </Banner> )} {error && ( <Banner tone="critical" dismissible> Failed to initiate sync. Please try again. </Banner> )} <Section heading="Sync settings"> <BlockStack> <Select label="Platform" value={platform} onChange={setPlatform} options={platforms} /> <Select label="Sync type" value={syncType} onChange={setSyncType} options={syncTypes} /> <Banner tone="info"> This will sync all customers in the background. Large customer lists may take several minutes to complete. </Banner> </BlockStack> </Section> </AdminAction> ); }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(); } );
Anchor to Customer index action (should render) ,[object Object]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.
Supported components
Available APIs
Supported components
Available APIs
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
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
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
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
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}; } } );
Anchor to Customer index selection action ,[object Object]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. The action renders in a modal overlay designed for batch processing.
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
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
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, () => <App />); 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 ( <AdminAction title="Export to Marketing Platform" primaryAction={ <Button onPress={handleExport} disabled={loading || success || !listId} > {loading ? 'Exporting...' : 'Export Customers'} </Button> } secondaryAction={ <Button onPress={close}> Cancel </Button> } > {success && ( <Banner tone="success" dismissible> {customerCount} customers exported successfully! </Banner> )} {error && ( <Banner tone="critical" dismissible> Failed to export customers. Please try again. </Banner> )} <Section heading="Export settings"> <BlockStack> <Text> Exporting {customerCount} selected customer{customerCount !== 1 ? 's' : ''} </Text> <Select label="Marketing list" value={listId} onChange={setListId} options={lists} /> </BlockStack> </Section> </AdminAction> ); }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
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, () => <App />); 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 ( <AdminAction title="Bulk Tag Customers" primaryAction={ <Button onPress={handleTag} disabled={loading || success || !tags.trim()} > {loading ? 'Applying Tags...' : 'Apply Tags'} </Button> } secondaryAction={ <Button onPress={close}> Cancel </Button> } > {success && ( <Banner tone="success" dismissible> Tags applied to {customerCount} customers! </Banner> )} {error && ( <Banner tone="critical" dismissible> Failed to apply tags. Please try again. </Banner> )} <Section heading="Tag settings"> <BlockStack> <Text> Apply tags to {customerCount} selected customer{customerCount !== 1 ? 's' : ''} </Text> <TextField label="Tags (comma-separated)" value={tags} onChange={setTags} placeholder="VIP, Newsletter, Holiday2024" /> <Text tone="subdued"> Enter one or more tags separated by commas </Text> </BlockStack> </Section> </AdminAction> ); }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(); } );
Anchor to Customer index selection action (should render) ,[object Object]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.
Supported components
Available APIs
Supported components
Available APIs
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
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
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
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
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}; } } );
Anchor to Customer segment targetsCustomer segment targets
Use action and runnable targets 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.
Anchor to Customer segment details action ,[object Object]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. The action renders in a modal overlay, providing space for segment-specific workflows.
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
Supported components
- Admin
Action - Admin
Block - Admin
Print Action - Badge
- Banner
- Block
Stack - Box
- Button
- Checkbox
- Choice
List - Color
Picker - Date
Field - Date
Picker - Divider
- Email
Field - Form
- Function
Settings - Heading
- Heading
Group - Icon
- Image
- Inline
Stack - Link
- Money
Field - Number
Field - Paragraph
- Password
Field - Pressable
- Progress
Indicator - Section
- Select
- Text
- Text
Area - Text
Field - URLField
Available APIs
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
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, () => <App />); 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 ( <AdminAction title="Export to Email Platform"> <BlockStack> <ProgressIndicator size="small-100" /> <Text>Loading segment details...</Text> </BlockStack> </AdminAction> ); } return ( <AdminAction title="Export to Email Platform" primaryAction={ <Button onPress={handleExport} disabled={loading || success || !audienceName.trim()} > {loading ? 'Exporting...' : 'Create Audience'} </Button> } secondaryAction={ <Button onPress={close}> Cancel </Button> } > {success && ( <Banner tone="success" dismissible> Segment exported successfully! Audience "{audienceName}" created. </Banner> )} {error && ( <Banner tone="critical" dismissible> Failed to export segment. Please try again. </Banner> )} <Section heading="Segment information"> <BlockStack> <Box> <Heading>Source segment</Heading> <Text>{segmentName}</Text> </Box> <Box> <Heading>Customer count</Heading> <Text>{customerCount.toLocaleString()} customers</Text> </Box> <TextField label="Audience name" value={audienceName} onChange={setAudienceName} /> </BlockStack> </Section> </AdminAction> ); }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
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, () => <App />); 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 ( <AdminAction title="Create Ad Campaign" primaryAction={ <Button onPress={handleCreate} disabled={loading || success || !campaignName.trim()} > {loading ? 'Creating...' : 'Create Campaign'} </Button> } secondaryAction={ <Button onPress={close}> Cancel </Button> } > {success && ( <Banner tone="success" dismissible> Campaign created successfully! </Banner> )} {error && ( <Banner tone="critical" dismissible> Failed to create campaign. Please try again. </Banner> )} <Section heading="Campaign settings"> <BlockStack> <TextField label="Campaign name" value={campaignName} onChange={setCampaignName} placeholder="Holiday promotion for high-value customers" /> <TextField label="Daily budget ($)" type="number" value={budget} onChange={setBudget} /> <Select label="Campaign duration" value={duration} onChange={setDuration} options={durations} /> </BlockStack> </Section> </AdminAction> ); }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(); } );
Anchor to Customer segment details action (should render) ,[object Object]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.
Supported components
Available APIs
Supported components
Available APIs
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
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
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
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
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}; } } );
Anchor to Customer segmentation templates ,[object Object]Customer segmentation templates target
admin.customers.segmentation-templates.render
Use this target to provide a customer segments 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 to return template data. Templates appear in the segment editor's template gallery and can be inserted with a single click.
Supported components
Available APIs
Supported components
Available APIs
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
import { reactExtension, CustomerSegmentTemplate, } from '@shopify/ui-extensions-react/admin'; const TARGET = 'admin.customers.segmentation-templates.render'; export default reactExtension(TARGET, () => <App />); function App() { return ( <> <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" /> <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" /> <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" /> </> ); }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
import { reactExtension, CustomerSegmentTemplate, } from '@shopify/ui-extensions-react/admin'; const TARGET = 'admin.customers.segmentation-templates.render'; export default reactExtension(TARGET, () => <App />); function App() { return ( <> <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" /> <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" /> <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" /> <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" /> </> ); }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
import { reactExtension, CustomerSegmentTemplate, } from '@shopify/ui-extensions-react/admin'; const TARGET = 'admin.customers.segmentation-templates.render'; export default reactExtension(TARGET, () => <App />); function App() { return ( <> <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" /> <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" /> <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" /> <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" /> </> ); }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(); } );
Anchor to Best practicesBest 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 status and provide merchants with information about consent requirements to maintain compliance.
- Support segmentation use cases: When creating customer segment templates, focus on actionable segments that help merchants make business decisions. Include clear descriptions explaining when and why to use each template.
Anchor to LimitationsLimitations
- Single target per module: Each
[[extensions.targeting]]entry in your TOML 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 before deploying them to ensure they work correctly and return expected results.
- Segment action location: The
admin.customer-segment-details.action.rendertarget appears under the Use segment button, not in More actions. - Block target visibility: Block extensions must be manually added and pinned by merchants before they appear.
- Block collapse behavior: Returning
nullfrom a block extension collapses the block rather than removing it from the page. Blocks can't be fully hidden at runtime.