10 KiB
10 KiB
Extensions Reference
Guide for building UI extensions and Shopify Functions.
Checkout UI Extensions
Customize checkout and thank-you pages with native-rendered components.
Extension Points
Block Targets (Merchant-Configurable):
purchase.checkout.block.render- Main checkoutpurchase.thank-you.block.render- Thank you page
Static Targets (Fixed Position):
purchase.checkout.header.render-afterpurchase.checkout.contact.render-beforepurchase.checkout.shipping-option-list.render-afterpurchase.checkout.payment-method-list.render-afterpurchase.checkout.footer.render-before
Setup
shopify app generate extension --type checkout_ui_extension
Configuration (shopify.extension.toml):
api_version = "2025-01"
name = "gift-message"
type = "ui_extension"
[[extensions.targeting]]
target = "purchase.checkout.block.render"
[capabilities]
network_access = true
api_access = true
Basic Example
import { reactExtension, BlockStack, TextField, Checkbox, useApi } from '@shopify/ui-extensions-react/checkout';
export default reactExtension('purchase.checkout.block.render', () => <Extension />);
function Extension() {
const [message, setMessage] = useState('');
const [isGift, setIsGift] = useState(false);
const { applyAttributeChange } = useApi();
useEffect(() => {
if (isGift) {
applyAttributeChange({
type: 'updateAttribute',
key: 'gift_message',
value: message
});
}
}, [message, isGift]);
return (
<BlockStack spacing="loose">
<Checkbox checked={isGift} onChange={setIsGift}>
This is a gift
</Checkbox>
{isGift && (
<TextField
label="Gift Message"
value={message}
onChange={setMessage}
multiline={3}
/>
)}
</BlockStack>
);
}
Common Hooks
useApi:
const { extensionPoint, shop, storefront, i18n, sessionToken } = useApi();
useCartLines:
const lines = useCartLines();
lines.forEach(line => {
console.log(line.merchandise.product.title, line.quantity);
});
useShippingAddress:
const address = useShippingAddress();
console.log(address.city, address.countryCode);
useApplyCartLinesChange:
const applyChange = useApplyCartLinesChange();
async function addItem() {
await applyChange({
type: 'addCartLine',
merchandiseId: 'gid://shopify/ProductVariant/123',
quantity: 1
});
}
Core Components
Layout:
BlockStack- Vertical stackingInlineStack- Horizontal layoutGrid,GridItem- Grid layoutView- ContainerDivider- Separator
Input:
TextField- Text inputCheckbox- BooleanSelect- DropdownDatePicker- Date selectionForm- Form wrapper
Display:
Text,Heading- TypographyBanner- MessagesBadge- StatusImage- ImagesLink- HyperlinksList,ListItem- Lists
Interactive:
Button- ActionsModal- OverlaysPressable- Click areas
Admin UI Extensions
Extend Shopify admin interface.
Admin Action
Custom actions on resource pages.
shopify app generate extension --type admin_action
import { reactExtension, AdminAction, Button } from '@shopify/ui-extensions-react/admin';
export default reactExtension('admin.product-details.action.render', () => <Extension />);
function Extension() {
const { data } = useData();
async function handleExport() {
const response = await fetch('/api/export', {
method: 'POST',
body: JSON.stringify({ productId: data.product.id })
});
console.log('Exported:', await response.json());
}
return (
<AdminAction
title="Export Product"
primaryAction={<Button onPress={handleExport}>Export</Button>}
/>
);
}
Targets:
admin.product-details.action.renderadmin.order-details.action.renderadmin.customer-details.action.render
Admin Block
Embedded content in admin pages.
import { reactExtension, BlockStack, Text, Badge } from '@shopify/ui-extensions-react/admin';
export default reactExtension('admin.product-details.block.render', () => <Extension />);
function Extension() {
const { data } = useData();
const [analytics, setAnalytics] = useState(null);
useEffect(() => {
fetchAnalytics(data.product.id).then(setAnalytics);
}, []);
return (
<BlockStack>
<Text variant="headingMd">Product Analytics</Text>
<Text>Views: {analytics?.views || 0}</Text>
<Text>Conversions: {analytics?.conversions || 0}</Text>
<Badge tone={analytics?.trending ? "success" : "info"}>
{analytics?.trending ? "Trending" : "Normal"}
</Badge>
</BlockStack>
);
}
Targets:
admin.product-details.block.renderadmin.order-details.block.renderadmin.customer-details.block.render
POS UI Extensions
Customize Point of Sale experience.
Smart Grid Tile
Quick access action on POS home screen.
import { reactExtension, SmartGridTile } from '@shopify/ui-extensions-react/pos';
export default reactExtension('pos.home.tile.render', () => <Extension />);
function Extension() {
function handlePress() {
// Navigate to custom workflow
}
return (
<SmartGridTile
title="Gift Cards"
subtitle="Manage gift cards"
onPress={handlePress}
/>
);
}
POS Modal
Full-screen workflow.
import { reactExtension, Screen, BlockStack, Button, TextField } from '@shopify/ui-extensions-react/pos';
export default reactExtension('pos.home.modal.render', () => <Extension />);
function Extension() {
const { navigation } = useApi();
const [amount, setAmount] = useState('');
function handleIssue() {
// Issue gift card
navigation.pop();
}
return (
<Screen name="Gift Card" title="Issue Gift Card">
<BlockStack>
<TextField label="Amount" value={amount} onChange={setAmount} />
<TextField label="Recipient Email" />
<Button onPress={handleIssue}>Issue</Button>
</BlockStack>
</Screen>
);
}
Customer Account Extensions
Customize customer account pages.
Order Status Extension
import { reactExtension, BlockStack, Text, Button } from '@shopify/ui-extensions-react/customer-account';
export default reactExtension('customer-account.order-status.block.render', () => <Extension />);
function Extension() {
const { order } = useApi();
function handleReturn() {
// Initiate return
}
return (
<BlockStack>
<Text variant="headingMd">Need to return?</Text>
<Text>Start return for order {order.name}</Text>
<Button onPress={handleReturn}>Start Return</Button>
</BlockStack>
);
}
Targets:
customer-account.order-status.block.rendercustomer-account.order-index.block.rendercustomer-account.profile.block.render
Shopify Functions
Serverless backend customization.
Function Types
Discounts:
order_discount- Order-level discountsproduct_discount- Product-specific discountsshipping_discount- Shipping discounts
Payment Customization:
- Hide/rename/reorder payment methods
Delivery Customization:
- Custom shipping options
- Delivery rules
Validation:
- Cart validation rules
- Checkout validation
Create Function
shopify app generate extension --type function
Order Discount Function
// input.graphql
query Input {
cart {
lines {
quantity
merchandise {
... on ProductVariant {
product {
hasTag(tag: "bulk-discount")
}
}
}
}
}
}
// function.js
export default function orderDiscount(input) {
const targets = input.cart.lines
.filter(line => line.merchandise.product.hasTag)
.map(line => ({
productVariant: { id: line.merchandise.id }
}));
if (targets.length === 0) {
return { discounts: [] };
}
return {
discounts: [{
targets,
value: {
percentage: {
value: 10 // 10% discount
}
}
}]
};
}
Payment Customization Function
export default function paymentCustomization(input) {
const hidePaymentMethods = input.cart.lines.some(
line => line.merchandise.product.hasTag
);
if (!hidePaymentMethods) {
return { operations: [] };
}
return {
operations: [{
hide: {
paymentMethodId: "gid://shopify/PaymentMethod/123"
}
}]
};
}
Validation Function
export default function cartValidation(input) {
const errors = [];
// Max 5 items per cart
if (input.cart.lines.length > 5) {
errors.push({
localizedMessage: "Maximum 5 items allowed per order",
target: "cart"
});
}
// Min $50 for wholesale
const isWholesale = input.cart.lines.some(
line => line.merchandise.product.hasTag
);
if (isWholesale && input.cart.cost.totalAmount.amount < 50) {
errors.push({
localizedMessage: "Wholesale orders require $50 minimum",
target: "cart"
});
}
return { errors };
}
Network Requests
Extensions can call external APIs.
import { useApi } from '@shopify/ui-extensions-react/checkout';
function Extension() {
const { sessionToken } = useApi();
async function fetchData() {
const token = await sessionToken.get();
const response = await fetch('https://your-app.com/api/data', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
return await response.json();
}
}
Best Practices
Performance:
- Lazy load data
- Memoize expensive computations
- Use loading states
- Minimize re-renders
UX:
- Provide clear error messages
- Show loading indicators
- Validate inputs
- Support keyboard navigation
Security:
- Verify session tokens on backend
- Sanitize user input
- Use HTTPS for all requests
- Don't expose sensitive data
Testing:
- Test on development stores
- Verify mobile/desktop
- Check accessibility
- Test edge cases
Resources
- Checkout Extensions: https://shopify.dev/docs/api/checkout-extensions
- Admin Extensions: https://shopify.dev/docs/apps/admin/extensions
- Functions: https://shopify.dev/docs/apps/functions
- Components: https://shopify.dev/docs/api/checkout-ui-extensions/components