--- title: Gift cards description: >- Gift card details pages display balance, status, expiration dates, and transaction history for individual gift cards. Extensions help merchants manage gift card workflows, track usage patterns, and integrate with external loyalty or CRM systems. api_version: 2025-07 api_name: admin-extensions source_url: html: 'https://shopify.dev/docs/api/admin-extensions/2025-07/targets/gift-cards' md: 'https://shopify.dev/docs/api/admin-extensions/2025-07/targets/gift-cards.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. # Gift cards Gift card details pages display balance, status, expiration dates, and transaction history for individual [gift cards](https://help.shopify.com/manual/products/gift-card-products). Extensions on these pages help merchants manage gift card workflows, track usage patterns, and integrate with external loyalty or CRM systems. ### Use cases * **Balance adjustments:** Merchants can add promotional credits or make balance corrections with full audit trails. * **Fraud detection:** Display risk indicators and suspicious activity alerts to help merchants identify potentially fraudulent gift cards. * **Customer lookup:** Show customer purchase history and gift card usage patterns to provide better customer service. * **Expiration management:** Merchants can extend expiration dates or send reminder notifications to gift card holders. * **Transfer ownership:** Merchants can reassign gift cards to different customers when needed. ![Shopify admin gift card pages showing all available extension target locations.](https://shopify.dev/assets/assets/images/templated-apis-screenshots/admin-extensions/targets-overview-images/admin.gift-card.overview-BbM4USBq.png) *** ## Gift cards targets Use [action and block targets](https://shopify.dev/docs/api/admin-extensions/2025-07#building-your-extension) to extend the gift cards details page with workflows and contextual information. Action targets open as modal overlays from the **More actions** menu, while block targets display as inline cards. Extensions can query and mutate Shopify data using the [direct API](https://shopify.dev/docs/api/admin-extensions/2025-07#direct-api-access), or call your [app's backend](https://shopify.dev/docs/api/admin-extensions/2025-07#app-authentication) for custom business logic and external integrations. ### Gift card details action target `admin.gift-card-details.action.render` Renders an admin action extension on the gift cards details page. Merchants can access this extension from the **More actions** menu. Use this target to provide workflows that operate on gift cards data, such as syncing with external systems, exporting gift cards information, or managing credit terms. Extensions at this target can access gift card data 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 * [Admin​Action](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminaction) * [Admin​Block](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminblock) * [Admin​Print​Action](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) * [Block​Stack](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) * [Choice​List](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/choicelist) * [Color​Picker](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/colorpicker) * [Date​Field](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/datefield) * [Date​Picker](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) * [Email​Field](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) * [Function​Settings](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) * [Heading​Group](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) * [Inline​Stack](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) * [Money​Field](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/moneyfield) * [Number​Field](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) * [Password​Field](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) * [Progress​Indicator](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) * [Text​Area](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/textarea) * [Text​Field](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 gift card transaction history and details to a CSV file. This example demonstrates calling your app backend to generate a downloadable export with customizable options for date range and data fields. ##### React ```tsx import React from 'react'; import { useState } from 'react'; import { reactExtension, useApi, AdminAction, Banner, BlockStack, Box, Button, Checkbox, ChoiceList, Divider, } from '@shopify/ui-extensions-react/admin'; const TARGET = 'admin.gift-card-details.action.render'; export default reactExtension(TARGET, () => ); function App() { const { data, close } = useApi(TARGET); const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(false); const [error, setError] = useState(null); const [dateRange, setDateRange] = useState(['all']); const [includeCustomerInfo, setIncludeCustomerInfo] = useState(true); const [includeTransactions, setIncludeTransactions] = useState(true); const handleExport = async () => { setLoading(true); setError(null); const giftCardId = data.selected[0].id; try { const response = await fetch('https://your-app.com/api/gift-cards/export', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ giftCardId, dateRange: dateRange[0], includeCustomerInfo, includeTransactions, }), }); if (response.ok) { const blob = await response.blob(); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `gift-card-${giftCardId.split('/').pop()}.csv`; link.click(); setSuccess(true); close(); } else { const result = await response.json(); setError(result.message || 'Export failed'); } } catch (err) { setError('Failed to connect to export service'); } finally { setLoading(false); } }; return ( {loading ? 'Generating...' : 'Export CSV'} } secondaryAction={} > {success && Export downloaded successfully!} {error && {error}} Include customer information Include transaction history ); } ``` ##### TS ```tsx import { extension, AdminAction, Banner, BlockStack, Box, Button, Checkbox, ChoiceList, Divider, } from '@shopify/ui-extensions/admin'; export default extension( 'admin.gift-card-details.action.render', (root, api) => { let loading = false; let success = false; let error: string | null = null; let dateRange = ['all']; let includeCustomerInfo = true; let includeTransactions = true; const content = root.createFragment(); const primaryAction = root.createFragment(); const secondaryAction = root.createFragment(); const handleExport = async () => { loading = true; error = null; updateUI(); const giftCardId = api.data.selected[0].id; try { const response = await fetch('https://your-app.com/api/gift-cards/export', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ giftCardId, dateRange: dateRange[0], includeCustomerInfo, includeTransactions, }), }); if (response.ok) { success = true; updateUI(); api.close(); } else { const result = await response.json(); error = result.message || 'Export failed'; } } catch (err) { error = 'Failed to connect to export service'; } finally { loading = false; updateUI(); } }; const updateUI = () => { content.replaceChildren(); primaryAction.replaceChildren(); primaryAction.appendChild( root.createComponent( Button, { onPress: handleExport, disabled: loading || success }, loading ? 'Generating...' : 'Export CSV' ) ); const stack = root.createComponent(BlockStack, { gap: 'base' }); if (success) { stack.appendChild( root.createComponent(Banner, { tone: 'success' }, 'Export downloaded successfully!') ); } if (error) { stack.appendChild(root.createComponent(Banner, { tone: 'critical' }, error)); } stack.appendChild( root.createComponent( Box, {}, root.createComponent(ChoiceList, { title: 'Date Range', name: 'dateRange', choices: [ { label: 'All time', id: 'all' }, { label: 'Last 30 days', id: '30days' }, { label: 'Last 90 days', id: '90days' }, { label: 'This year', id: 'year' }, ], value: dateRange, onChange: (val: string[]) => { dateRange = val; updateUI(); }, }) ) ); stack.appendChild(root.createComponent(Divider, {})); const checkboxStack = root.createComponent(BlockStack, { gap: 'base' }); checkboxStack.appendChild( root.createComponent( Checkbox, { checked: includeCustomerInfo, onChange: (val: boolean) => { includeCustomerInfo = val; updateUI(); }, }, 'Include customer information' ) ); checkboxStack.appendChild( root.createComponent( Checkbox, { checked: includeTransactions, onChange: (val: boolean) => { includeTransactions = val; updateUI(); }, }, 'Include transaction history' ) ); stack.appendChild(checkboxStack); content.appendChild(stack); }; secondaryAction.appendChild( root.createComponent(Button, { onPress: () => api.close() }, 'Cancel') ); updateUI(); const adminAction = root.createComponent(AdminAction, { title: 'Export Gift Card Data', primaryAction, secondaryAction, }); adminAction.appendChild(content); root.appendChild(adminAction); root.mount(); } ); ``` * #### ##### Description Add an action extension that fetches gift card details using the \[direct API]\(/docs/api/admin-extensions/2025-07#direct-api-access) and syncs the balance and status with an external loyalty management system. This example demonstrates using the query() API to read gift card data directly from Shopify. ##### React ```tsx import React, { useState, useEffect } from 'react'; import { reactExtension, useApi, AdminAction, Banner, BlockStack, Box, Button, Checkbox, Divider, } from '@shopify/ui-extensions-react/admin'; const TARGET = 'admin.gift-card-details.action.render'; export default reactExtension(TARGET, () => ); function App() { const { data, close, query } = useApi(TARGET); const [loading, setLoading] = useState(false); const [syncing, setSyncing] = useState(false); const [success, setSuccess] = useState(false); const [error, setError] = useState(''); const [giftCard, setGiftCard] = useState(null); const [syncBalance, setSyncBalance] = useState(true); const [syncStatus, setSyncStatus] = useState(true); const [notifyCustomer, setNotifyCustomer] = useState(false); useEffect(() => { fetchGiftCardDetails(); }, []); const fetchGiftCardDetails = async () => { setLoading(true); const giftCardId = data.selected[0].id; try { const result = await query( `query GetGiftCard($id: ID!) { giftCard(id: $id) { id balance { amount currencyCode } initialValue { amount currencyCode } enabled expiresOn customer { id displayName email } lastCharacters } }`, { variables: { id: giftCardId } } ); if (result.data?.giftCard) { setGiftCard(result.data.giftCard); } else { setError('Failed to load gift card details'); } } catch (err) { setError('Error fetching gift card data'); } finally { setLoading(false); } }; const handleSync = async () => { setSyncing(true); setError(''); try { const response = await fetch('https://your-loyalty-system.com/api/sync-gift-card', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ giftCardId: giftCard.id, balance: syncBalance ? giftCard.balance : null, status: syncStatus ? giftCard.enabled : null, customerEmail: giftCard.customer?.email, notifyCustomer, }), }); if (response.ok) { setSuccess(true); close(); } else { setError('Sync failed. Please try again.'); } } catch (err) { setError('Connection error. Check your network.'); } finally { setSyncing(false); } }; return ( {syncing ? 'Syncing...' : 'Sync Now'} } secondaryAction={} > {success && Gift card synced successfully!} {error && {error}} {loading && Loading gift card details...} {giftCard && ( <> Card ending in {giftCard.lastCharacters} • Balance: {giftCard.balance.amount} {giftCard.balance.currencyCode} Sync current balance Sync enabled/disabled status Notify customer of sync )} ); } ``` ##### TS ```tsx import { extension, AdminAction, Banner, BlockStack, Box, Button, Checkbox, Divider, } from '@shopify/ui-extensions/admin'; export default extension( 'admin.gift-card-details.action.render', (root, api) => { let loading = true; let syncing = false; let success = false; let error = ''; let giftCard: any = null; let syncBalance = true; let syncStatus = true; let notifyCustomer = false; const content = root.createFragment(); const primaryAction = root.createFragment(); const secondaryAction = root.createFragment(); const updateUI = () => { content.replaceChildren(); if (success) { content.appendChild(root.createComponent(Banner, { tone: 'success' }, 'Gift card synced successfully!')); } if (error) { content.appendChild(root.createComponent(Banner, { tone: 'critical' }, error)); } if (loading) { content.appendChild(root.createComponent(Banner, { tone: 'info' }, 'Loading gift card details...')); } if (giftCard) { const stack = root.createComponent(BlockStack, { gap: 'base' }); const infoBox = root.createComponent(Box, { padding: 'base' }); infoBox.appendChild( root.createComponent(Banner, { tone: 'info' }, `Card ending in ${giftCard.lastCharacters} • Balance: ${giftCard.balance.amount} ${giftCard.balance.currencyCode}` ) ); stack.appendChild(infoBox); stack.appendChild(root.createComponent(Divider)); const optionsBox = root.createComponent(Box, { padding: 'base' }); const optionsStack = root.createComponent(BlockStack, { gap: 'base' }); optionsStack.appendChild( root.createComponent(Checkbox, { checked: syncBalance, onChange: (val: boolean) => { syncBalance = val; updateUI(); } }, 'Sync current balance') ); optionsStack.appendChild( root.createComponent(Checkbox, { checked: syncStatus, onChange: (val: boolean) => { syncStatus = val; updateUI(); } }, 'Sync enabled/disabled status') ); optionsStack.appendChild( root.createComponent(Checkbox, { checked: notifyCustomer, onChange: (val: boolean) => { notifyCustomer = val; updateUI(); } }, 'Notify customer of sync') ); optionsBox.appendChild(optionsStack); stack.appendChild(optionsBox); content.appendChild(stack); } primaryAction.replaceChildren(); primaryAction.appendChild( root.createComponent(Button, { onPress: handleSync, disabled: loading || syncing || success || !giftCard }, syncing ? 'Syncing...' : 'Sync Now') ); }; const fetchGiftCardDetails = async () => { const giftCardId = api.data.selected[0].id; try { const result = await api.query( `query GetGiftCard($id: ID!) { giftCard(id: $id) { id balance { amount currencyCode } initialValue { amount currencyCode } enabled expiresOn customer { id displayName email } lastCharacters } }`, { variables: { id: giftCardId } } ); if (result.data?.giftCard) { giftCard = result.data.giftCard; } else { error = 'Failed to load gift card details'; } } catch (err) { error = 'Error fetching gift card data'; } finally { loading = false; updateUI(); } }; const handleSync = async () => { syncing = true; error = ''; updateUI(); try { const response = await fetch('https://your-loyalty-system.com/api/sync-gift-card', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ giftCardId: giftCard.id, balance: syncBalance ? giftCard.balance : null, status: syncStatus ? giftCard.enabled : null, customerEmail: giftCard.customer?.email, notifyCustomer, }), }); if (response.ok) { success = true; updateUI(); api.close(); } else { error = 'Sync failed. Please try again.'; } } catch (err) { error = 'Connection error. Check your network.'; } finally { syncing = false; updateUI(); } }; secondaryAction.appendChild( root.createComponent(Button, { onPress: () => api.close() }, 'Cancel') ); updateUI(); fetchGiftCardDetails(); const adminAction = root.createComponent(AdminAction, { title: 'Sync with Loyalty System', primaryAction, secondaryAction, }); adminAction.appendChild(content); root.appendChild(adminAction); root.mount(); } ); ``` ### Gift card details action (should render) target `admin.gift-card-details.action.should-render` Controls the render state of an admin action extension on the gift cards details page. Use this target to conditionally show or hide your action extension based on the gift card's properties, such as status, configuration, or specific business requirements. This target returns a boolean value that determines whether the corresponding action extension appears in the **More actions** menu. The extension evaluates 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 Add a should-render extension that checks if external analytics data exists for a gift card in your app backend. This example demonstrates calling your custom API to determine if usage analytics and redemption history are available for display. ##### React ```tsx import {extension} from '@shopify/ui-extensions/admin'; const TARGET = 'admin.gift-card-details.action.should-render'; export default extension(TARGET, async ({data}) => { const giftCardId = data.selected[0].id; const numericId = giftCardId.split('/').pop(); try { // Check if external analytics exist for this gift card const response = await fetch( `https://your-app.com/api/gift-cards/${numericId}/analytics-available`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, } ); if (!response.ok) { return {display: false}; } const analyticsData = await response.json(); // Show action if analytics data exists and has meaningful content const hasAnalytics = analyticsData.hasRedemptionHistory || analyticsData.hasUsageMetrics || analyticsData.trackingEnabled; return {display: hasAnalytics}; } catch (err) { // Hide action if backend is unavailable return {display: false}; } }); ``` ##### TS ```tsx import {extension} from '@shopify/ui-extensions/admin'; const TARGET = 'admin.gift-card-details.action.should-render'; export default extension(TARGET, async ({data}) => { const giftCardId = data.selected[0].id; const numericId = giftCardId.split('/').pop(); try { // Check if external analytics exist for this gift card const response = await fetch( `https://your-app.com/api/gift-cards/${numericId}/analytics-available`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, } ); if (!response.ok) { return {display: false}; } const analyticsData = await response.json(); // Show action if analytics data exists and has meaningful content const hasAnalytics = analyticsData.hasRedemptionHistory || analyticsData.hasUsageMetrics || analyticsData.trackingEnabled; return {display: hasAnalytics}; } catch (err) { // Hide action if backend is unavailable return {display: false}; } }); ``` * #### ##### Description Add a should-render extension that displays a status dashboard action only for gift cards that are enabled and have remaining balance, using the \[direct API]\(/docs/api/admin-extensions/2025-07#direct-api-access) to check gift card status and balance. ##### React ```tsx import {extension} from '@shopify/ui-extensions/admin'; export default extension( 'admin.gift-card-details.action.should-render', async ({data, query}) => { const giftCardId = data.selected[0].id; try { const {data: responseData, errors} = await query(` query GetGiftCardStatus($id: ID!) { giftCard(id: $id) { enabled balance { amount currencyCode } expiresOn } } `, { variables: { id: giftCardId } }); if (errors?.length) { console.error('GraphQL errors:', errors); return { display: false }; } const giftCard = responseData?.giftCard; if (!giftCard) { return { display: false }; } // Show dashboard only for enabled gift cards with remaining balance const hasBalance = parseFloat(giftCard.balance?.amount || '0') > 0; const isEnabled = giftCard.enabled === true; // Also check if not expired const isNotExpired = !giftCard.expiresOn || new Date(giftCard.expiresOn) > new Date(); return { display: isEnabled && hasBalance && isNotExpired }; } catch (err) { console.error('Failed to check gift card status:', err); return { display: false }; } } ); ``` ##### TS ```tsx import {extension} from '@shopify/ui-extensions/admin'; export default extension( 'admin.gift-card-details.action.should-render', async ({data, query}) => { const giftCardId = data.selected[0].id; try { const {data: responseData, errors} = await query(` query GetGiftCardStatus($id: ID!) { giftCard(id: $id) { enabled balance { amount currencyCode } expiresOn } } `, { variables: { id: giftCardId } }); if (errors?.length) { console.error('GraphQL errors:', errors); return { display: false }; } const giftCard = responseData?.giftCard; if (!giftCard) { return { display: false }; } // Show dashboard only for enabled gift cards with remaining balance const hasBalance = parseFloat(giftCard.balance?.amount || '0') > 0; const isEnabled = giftCard.enabled === true; // Also check if not expired const isNotExpired = !giftCard.expiresOn || new Date(giftCard.expiresOn) > new Date(); return { display: isEnabled && hasBalance && isNotExpired }; } catch (err) { console.error('Failed to check gift card status:', err); return { display: false }; } } ); ``` ### Gift card details block target `admin.gift-card-details.block.render` Renders an admin block extension inline on the gift cards details page. Use this target to display contextual information, analytics, or status updates related to the gift cards without requiring merchant interaction to open a modal. Extensions at this target can access gift card data through the `data` property in the [Block Extension API](https://shopify.dev/docs/api/admin-extensions/2025-07/target-apis/core-apis/block-extension-api). Blocks appear as cards on the page and can show real-time data, insights, or quick actions, providing persistent visibility for information merchants need to see at a glance. ### Support Components (35) APIs (1) ### Supported components * [Admin​Action](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminaction) * [Admin​Block](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/settings-and-templates/adminblock) * [Admin​Print​Action](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) * [Block​Stack](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) * [Choice​List](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/choicelist) * [Color​Picker](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/colorpicker) * [Date​Field](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/datefield) * [Date​Picker](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) * [Email​Field](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) * [Function​Settings](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) * [Heading​Group](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) * [Inline​Stack](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) * [Money​Field](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/moneyfield) * [Number​Field](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) * [Password​Field](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) * [Progress​Indicator](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) * [Text​Area](https://shopify.dev/docs/api/admin-extensions/2025-07/ui-components/forms/textarea) * [Text​Field](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 displays fraud risk analysis from an external fraud detection service. This example shows how to fetch and display external risk data with visual indicators for gift card security monitoring. ##### React ```tsx import React, { useState, useEffect } from 'react'; import { reactExtension, useApi, AdminBlock, Banner, BlockStack, Box, Button, Divider, Heading, Icon, Text, } from '@shopify/ui-extensions-react/admin'; const TARGET = 'admin.gift-card-details.block.render'; export default reactExtension(TARGET, () => ); function App() { const { data, query } = useApi(TARGET); const [loading, setLoading] = useState(true); const [riskData, setRiskData] = useState(null); const [error, setError] = useState(null); const fetchFraudAnalysis = async () => { setLoading(true); setError(null); try { const giftCardResponse = await query(` query GetGiftCard($id: ID!) { giftCard(id: $id) { id lastCharacters balance { amount currencyCode } customer { email } } } `, { variables: { id: data.selected[0].id } }); const giftCard = giftCardResponse?.data?.giftCard; const response = await fetch('https://your-app.com/api/fraud-check', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ giftCardId: data.selected[0].id, lastCharacters: giftCard?.lastCharacters, balance: giftCard?.balance?.amount, customerEmail: giftCard?.customer?.email, }), }); if (!response.ok) throw new Error('Fraud check failed'); const result = await response.json(); setRiskData(result); } catch (err) { setError('Unable to fetch fraud analysis'); } finally { setLoading(false); } }; useEffect(() => { fetchFraudAnalysis(); }, []); const getRiskIcon = (level) => { if (level === 'low') return 'CheckCircleFill'; if (level === 'medium') return 'AlertCircle'; return 'AlertTriangleFill'; }; return ( {loading && Analyzing gift card...} {error && ( {error} )} {riskData && ( Risk Level: {riskData.riskLevel.toUpperCase()} Score: {riskData.score}/100 Risk Factors: {riskData.factors?.map((factor, i) => ( • {factor} ))} Last checked: {new Date(riskData.checkedAt).toLocaleString()} )} ); } ``` ##### TS ```tsx import { extension, AdminBlock, Banner, BlockStack, Box, Button, Divider, Heading, Icon, Text, } from '@shopify/ui-extensions/admin'; export default extension( 'admin.gift-card-details.block.render', (root, api) => { let loading = true; let riskData = null; let error = null; const content = root.createFragment(); const fetchFraudAnalysis = async () => { loading = true; error = null; updateUI(); try { const giftCardResponse = await api.query(` query GetGiftCard($id: ID!) { giftCard(id: $id) { id lastCharacters balance { amount currencyCode } customer { email } } } `, { variables: { id: api.data.selected[0].id } }); const giftCard = giftCardResponse?.data?.giftCard; const response = await fetch('https://your-app.com/api/fraud-check', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ giftCardId: api.data.selected[0].id, lastCharacters: giftCard?.lastCharacters, balance: giftCard?.balance?.amount, customerEmail: giftCard?.customer?.email, }), }); if (!response.ok) throw new Error('Fraud check failed'); riskData = await response.json(); } catch (err) { error = 'Unable to fetch fraud analysis'; } finally { loading = false; updateUI(); } }; const getRiskIcon = (level) => { if (level === 'low') return 'CheckCircleFill'; if (level === 'medium') return 'AlertCircle'; return 'AlertTriangleFill'; }; const updateUI = () => { content.replaceChildren(); if (loading) { content.appendChild(root.createComponent(Text, {}, 'Analyzing gift card...')); return; } if (error) { content.appendChild(root.createComponent(Banner, { tone: 'critical' }, error)); return; } if (riskData) { const stack = root.createComponent(BlockStack, { gap: 'base' }); const riskBox = root.createComponent(Box, { padding: 'base', background: riskData.riskLevel === 'low' ? 'success' : riskData.riskLevel === 'medium' ? 'warning' : 'critical', }); const riskStack = root.createComponent(BlockStack, { gap: 'tight' }); riskStack.appendChild(root.createComponent(Icon, { name: getRiskIcon(riskData.riskLevel) })); riskStack.appendChild(root.createComponent(Heading, {}, `Risk Level: ${riskData.riskLevel.toUpperCase()}`)); riskStack.appendChild(root.createComponent(Text, {}, `Score: ${riskData.score}/100`)); riskBox.appendChild(riskStack); stack.appendChild(riskBox); stack.appendChild(root.createComponent(Divider, {})); const factorsStack = root.createComponent(BlockStack, { gap: 'tight' }); factorsStack.appendChild(root.createComponent(Text, { fontWeight: 'bold' }, 'Risk Factors:')); riskData.factors?.forEach((factor) => { factorsStack.appendChild(root.createComponent(Text, {}, `• ${factor}`)); }); stack.appendChild(factorsStack); stack.appendChild(root.createComponent(Divider, {})); stack.appendChild(root.createComponent(Text, { appearance: 'subdued' }, `Last checked: ${new Date(riskData.checkedAt).toLocaleString()}`)); stack.appendChild(root.createComponent(Button, { onPress: fetchFraudAnalysis }, 'Refresh Analysis')); content.appendChild(stack); } }; const adminBlock = root.createComponent(AdminBlock, { title: 'Fraud Risk Analysis' }); adminBlock.appendChild(content); root.appendChild(adminBlock); fetchFraudAnalysis(); root.mount(); } ); ``` * #### ##### Description Create a block extension that displays gift card details including balance, status, and customer information using the \[direct API]\(/docs/api/admin-extensions/2025-07#direct-api-access) query. This example demonstrates fetching gift card data with GraphQL and presenting key metrics in a dashboard format. ##### React ```tsx import React, { useState, useEffect } from 'react'; import { reactExtension, useApi, AdminBlock, Banner, BlockStack, Box, Button, Divider, Heading, Icon, } from '@shopify/ui-extensions-react/admin'; const TARGET = 'admin.gift-card-details.block.render'; export default reactExtension(TARGET, () => ); function App() { const { data, query } = useApi(TARGET); const [giftCard, setGiftCard] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchGiftCardDetails = async () => { setLoading(true); setError(null); try { const giftCardId = data.selected[0].id; const result = await query(` query GetGiftCard($id: ID!) { giftCard(id: $id) { id balance { amount currencyCode } initialValue { amount currencyCode } enabled expiresOn createdAt customer { displayName email } } } `, { variables: { id: giftCardId } }); if (result.data?.giftCard) { setGiftCard(result.data.giftCard); } else { setError('Gift card not found'); } } catch (err) { setError('Failed to load gift card details'); } finally { setLoading(false); } }; useEffect(() => { fetchGiftCardDetails(); }, []); if (loading) { return ( Loading gift card details... ); } if (error) { return ( {error} ); } const usedAmount = parseFloat(giftCard.initialValue.amount) - parseFloat(giftCard.balance.amount); const usagePercent = ((usedAmount / parseFloat(giftCard.initialValue.amount)) * 100).toFixed(0); return ( Balance Overview {` Current: ${giftCard.balance.currencyCode} ${giftCard.balance.amount}`} {`Initial: ${giftCard.initialValue.currencyCode} ${giftCard.initialValue.amount}`} {`Used: ${usagePercent}% (${giftCard.balance.currencyCode} ${usedAmount.toFixed(2)})`} Status {giftCard.enabled ? 'Active & Redeemable' : 'Disabled'} {giftCard.expiresOn && ( {`Expires: ${new Date(giftCard.expiresOn).toLocaleDateString()}`} )} Customer {giftCard.customer ? ( {giftCard.customer.displayName} {giftCard.customer.email} ) : ( No customer assigned )} ); } ``` ##### TS ```tsx import { extension, AdminBlock, Banner, BlockStack, Box, Button, Divider, Heading, Icon, } from '@shopify/ui-extensions/admin'; export default extension( 'admin.gift-card-details.block.render', (root, api) => { const content = root.createFragment(); let giftCard = null; let loading = true; let error = null; const fetchGiftCardDetails = async () => { loading = true; error = null; updateUI(); try { const giftCardId = api.data.selected[0].id; const result = await api.query(` query GetGiftCard($id: ID!) { giftCard(id: $id) { id balance { amount currencyCode } initialValue { amount currencyCode } enabled expiresOn createdAt customer { displayName email } } } `, { variables: { id: giftCardId } }); if (result.data?.giftCard) { giftCard = result.data.giftCard; } else { error = 'Gift card not found'; } } catch (err) { error = 'Failed to load gift card details'; } finally { loading = false; updateUI(); } }; const updateUI = () => { content.replaceChildren(); if (loading) { content.appendChild( root.createComponent(BlockStack, { gap: 'base' }, root.createComponent(Banner, { tone: 'info' }, 'Loading gift card details...') ) ); return; } if (error) { const errorStack = root.createComponent(BlockStack, { gap: 'base' }); errorStack.appendChild(root.createComponent(Banner, { tone: 'critical' }, error)); errorStack.appendChild(root.createComponent(Button, { onPress: fetchGiftCardDetails }, 'Retry')); content.appendChild(errorStack); return; } const usedAmount = parseFloat(giftCard.initialValue.amount) - parseFloat(giftCard.balance.amount); const usagePercent = ((usedAmount / parseFloat(giftCard.initialValue.amount)) * 100).toFixed(0); const mainStack = root.createComponent(BlockStack, { gap: 'base' }); // Balance section const balanceBox = root.createComponent(Box, { padding: 'base' }); const balanceStack = root.createComponent(BlockStack, { gap: 'tight' }); balanceStack.appendChild(root.createComponent(Heading, {}, 'Balance Overview')); const balanceDetails = root.createComponent(BlockStack, { gap: 'extraTight' }); balanceDetails.appendChild(root.createComponent(Box, {}, `Current: ${giftCard.balance.currencyCode} ${giftCard.balance.amount}`)); balanceDetails.appendChild(root.createComponent(Box, {}, `Initial: ${giftCard.initialValue.currencyCode} ${giftCard.initialValue.amount}`)); balanceDetails.appendChild(root.createComponent(Box, {}, `Used: ${usagePercent}% (${giftCard.balance.currencyCode} ${usedAmount.toFixed(2)})`)); balanceStack.appendChild(balanceDetails); balanceBox.appendChild(balanceStack); mainStack.appendChild(balanceBox); mainStack.appendChild(root.createComponent(Divider, {})); // Status section const statusBox = root.createComponent(Box, { padding: 'base' }); const statusStack = root.createComponent(BlockStack, { gap: 'tight' }); statusStack.appendChild(root.createComponent(Heading, {}, 'Status')); statusStack.appendChild(root.createComponent(Banner, { tone: giftCard.enabled ? 'success' : 'warning' }, giftCard.enabled ? 'Active & Redeemable' : 'Disabled')); if (giftCard.expiresOn) { statusStack.appendChild(root.createComponent(Box, {}, `Expires: ${new Date(giftCard.expiresOn).toLocaleDateString()}`)); } statusBox.appendChild(statusStack); mainStack.appendChild(statusBox); mainStack.appendChild(root.createComponent(Divider, {})); // Customer section const customerBox = root.createComponent(Box, { padding: 'base' }); const customerStack = root.createComponent(BlockStack, { gap: 'tight' }); customerStack.appendChild(root.createComponent(Heading, {}, 'Customer')); if (giftCard.customer) { customerStack.appendChild(root.createComponent(Box, {}, giftCard.customer.displayName)); customerStack.appendChild(root.createComponent(Box, {}, giftCard.customer.email)); } else { customerStack.appendChild(root.createComponent(Box, {}, 'No customer assigned')); } customerBox.appendChild(customerStack); mainStack.appendChild(customerBox); mainStack.appendChild(root.createComponent(Button, { onPress: fetchGiftCardDetails }, 'Refresh')); content.appendChild(mainStack); }; updateUI(); fetchGiftCardDetails(); const adminBlock = root.createComponent(AdminBlock, { title: 'Gift Card Status' }); adminBlock.appendChild(content); root.appendChild(adminBlock); root.mount(); } ); ``` *** ## Best practices * **Display balance context:** Always show both current balance and initial value when displaying gift card information. This helps merchants understand usage patterns and whether a gift card is partially used, fully depleted, or unused. * **Check enabled status before operations:** Gift cards can be [disabled](https://shopify.dev/docs/api/admin-graphql/latest/objects/GiftCard#field-GiftCard.fields.enabled) by merchants or automatically by the system. Before displaying actions like "send to customer" or "sync to external system," verify the gift card's `enabled` status to avoid operations on disabled cards. * **Handle balance adjustments with care:** When building extensions that modify gift card balances using [`giftCardCredit`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/giftCardCredit) or [`giftCardDebit`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/giftCardDebit), require explicit merchant confirmation and display the resulting balance before applying changes. Gift card balance errors can create customer service issues. * **Reconcile external integrations:** If your extension syncs gift cards with external loyalty platforms, ensure transaction histories stay synchronized. A mismatch between Shopify's balance and the external system's balance creates confusion and potential fraud issues. * **Respect expiration dates:** When displaying or exporting gift card data, prominently show expiration dates if set. Merchants need this information to proactively reach out to customers before cards expire and to ensure compliance with gift card regulations in their jurisdiction. *** ## 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. * **Gift card code immutability:** [Gift card](https://shopify.dev/docs/api/admin-graphql/latest/objects/GiftCard) codes cannot be modified after creation. The API only exposes [`lastCharacters`](https://shopify.dev/docs/api/admin-graphql/latest/objects/GiftCard#field-GiftCard.fields.lastCharacters) (final 4 digits) and [`maskedCode`](https://shopify.dev/docs/api/admin-graphql/latest/objects/GiftCard#field-GiftCard.fields.maskedCode). The [`giftCardUpdate` mutation](https://shopify.dev/docs/api/admin-graphql/latest/mutations/giftCardUpdate) has no code field. * **Permanent deactivation:** After you've used the [`giftCardDeactivate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/giftCardDeactivate) mutation, gift cards can't be re-enabled. * **Transaction history pagination:** Gift card transaction history returns a maximum of 250 transactions per request. * **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. ***