Loading...
Loading...
Guide for building Shopify POS (Point of Sale) UI extensions that integrate custom functionality into Shopify's retail interface. Use this skill when the user needs to create POS tiles, modals, blocks, or actions for the smart grid, cart, customer details, order details, product details, or post-purchase screens. Covers targets, components, APIs, and development workflow for POS app extensions.
npx skill4agent add toilahuongg/shopify-agents-kit shopify-posshopify app generate extension --template pos_ui --name "my-pos-extension"api_version = "2025-10"
[[extensions]]
type = "ui_extension"
name = "my-pos-extension"
handle = "my-pos-extension"
[[extensions.targeting]]
module = "./src/Tile.tsx"
target = "pos.home.tile.render"
[[extensions.targeting]]
module = "./src/Modal.tsx"
target = "pos.home.modal.render"| Type | Purpose | Example |
|---|---|---|
| Tile | Smart grid button on home screen | |
| Modal | Full-screen interface | |
| Block | Inline content section | |
| Menu Item | Action menu button | |
// Tile.tsx - Entry point on POS home
import { Tile, reactExtension } from '@shopify/ui-extensions-react/point-of-sale';
export default reactExtension('pos.home.tile.render', () => <TileComponent />);
function TileComponent() {
return <Tile title="My App" subtitle="Tap to open" enabled={true} />;
}// Modal.tsx - Launches when tile is tapped
import { Screen, Navigator, Text, Button, useApi, reactExtension } from '@shopify/ui-extensions-react/point-of-sale';
export default reactExtension('pos.home.modal.render', () => <ModalComponent />);
function ModalComponent() {
const api = useApi<'pos.home.modal.render'>();
return (
<Navigator>
<Screen name="Main" title="My Extension">
<Text>Welcome to my POS extension</Text>
<Button title="Close" onPress={() => api.navigation.dismiss()} />
</Screen>
</Navigator>
);
}// ProductBlock.tsx
import { Section, Text, reactExtension, useApi } from '@shopify/ui-extensions-react/point-of-sale';
export default reactExtension('pos.product-details.block.render', () => <ProductBlock />);
function ProductBlock() {
const { product } = useApi<'pos.product-details.block.render'>();
const productData = product.getProduct();
return (
<Section title="Custom Info">
<Text>Product ID: {productData?.id}</Text>
</Section>
);
}ScreenNavigatorScrollViewSectionStackListButtonTileSelectableTextFieldTextAreaNumberFieldEmailFieldDateFieldDatePickerRadioButtonListStepperPinPadBannerDialogBadgeIconImageCameraScannerimport { useApi } from '@shopify/ui-extensions-react/point-of-sale';
function MyComponent() {
const api = useApi<'pos.home.modal.render'>();
// Access various APIs based on target
const { cart, customer, session, navigation, toast } = api;
}const { cart } = useApi<'pos.home.modal.render'>();
// Add item
await cart.addLineItem({ variantId: 'gid://shopify/ProductVariant/123', quantity: 1 });
// Apply discount
await cart.applyCartDiscount({ type: 'percentage', value: 10, title: '10% Off' });
// Get cart
const currentCart = cart.getCart();const { session } = useApi<'pos.home.modal.render'>();
// Get session token for backend auth
const token = await session.getSessionToken();
// Get current staff member
const staff = session.currentSession;const { customer } = useApi<'pos.customer-details.block.render'>();
const customerData = customer.getCustomer();const { toast } = useApi<'pos.home.modal.render'>();
toast.show('Item added successfully');const { navigation } = useApi<'pos.home.modal.render'>();
navigation.dismiss(); // Close modal
navigation.navigate('ScreenName'); // Navigate to screenconst { scanner } = useApi<'pos.home.modal.render'>();
const result = await scanner.scanBarcode();const { print } = useApi<'pos.home.modal.render'>();
await print.printDocument(documentContent);2025-07const response = await fetch('shopify:admin/api/graphql.json', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `
query GetProduct($id: ID!) {
product(id: $id) {
title
variants(first: 10) {
nodes { id title inventoryQuantity }
}
}
}
`,
variables: { id: 'gid://shopify/Product/123' }
})
});shopify.app.toml[access_scopes]
scopes = "read_products,write_products,read_customers"shopify app devshopify app deployconst { storage } = useApi<'pos.home.modal.render'>();
// Store data
await storage.setItem('key', JSON.stringify(data));
// Retrieve data
const stored = await storage.getItem('key');
const data = stored ? JSON.parse(stored) : null;// Tile.tsx
import { Tile, reactExtension } from '@shopify/ui-extensions-react/point-of-sale';
export default reactExtension('pos.home.tile.render', () => (
<Tile title="Loyalty Points" subtitle="Check & redeem" enabled={true} />
));
// Modal.tsx
import {
Screen, Navigator, Text, Button, Section, Stack,
useApi, reactExtension
} from '@shopify/ui-extensions-react/point-of-sale';
import { useState, useEffect } from 'react';
export default reactExtension('pos.home.modal.render', () => <LoyaltyModal />);
function LoyaltyModal() {
const { cart, session, navigation, toast } = useApi<'pos.home.modal.render'>();
const [points, setPoints] = useState(0);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchPoints();
}, []);
async function fetchPoints() {
const token = await session.getSessionToken();
const currentCart = cart.getCart();
const customerId = currentCart?.customer?.id;
if (!customerId) {
setLoading(false);
return;
}
const res = await fetch('https://your-backend.com/api/points', {
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ customerId })
});
const data = await res.json();
setPoints(data.points);
setLoading(false);
}
async function redeemPoints() {
await cart.applyCartDiscount({
type: 'fixedAmount',
value: points / 100,
title: 'Loyalty Redemption'
});
toast.show('Points redeemed!');
navigation.dismiss();
}
return (
<Navigator>
<Screen name="Main" title="Loyalty Points" isLoading={loading}>
<Section title="Current Balance">
<Stack direction="vertical" spacing={2}>
<Text variant="headingLarge">{points} points</Text>
<Text>Worth ${(points / 100).toFixed(2)}</Text>
</Stack>
</Section>
<Button
title="Redeem All Points"
type="primary"
onPress={redeemPoints}
disabled={points === 0}
/>
<Button title="Close" onPress={() => navigation.dismiss()} />
</Screen>
</Navigator>
);
}