Shopify POS UI Extensions (2026)
Build custom extensions that integrate directly into Shopify's Point of Sale interface on iOS and Android devices.
Official References
-
POS UI Extensions API
-
Building for POS
-
POS Extension Targets
Prerequisites
-
Shopify CLI (latest)
-
Shopify App with POS enabled
-
Development store with POS Pro subscription
Enable POS embedding: In Partner Dashboard > App > Configuration, set "Embed app in Shopify POS" to True.
Extension Architecture
POS UI extensions have three interconnected parts:
-
Targets - Where your extension appears (tile, modal, block, menu item)
-
Target APIs - Data and functionality access (Cart, Customer, Session, etc.)
-
Components - Native UI building blocks (Button, Screen, List, etc.)
Creating a POS Extension
shopify app generate extension --template pos_ui --name "my-pos-extension"
Configuration (shopify.extension.toml)
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"
Targets Reference
See references/targets.md for all available targets.
Target Types
Type Purpose Example
Tile Smart grid button on home screen pos.home.tile.render
Modal Full-screen interface pos.home.modal.render
Block Inline content section pos.product-details.block.render
Menu Item Action menu button pos.customer-details.action.menu-item.render
Common Target Patterns
Home Screen (Smart Grid)
// 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 (Full Screen)
// 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> ); }
Block (Inline Content)
// 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> ); }
Components Reference
See references/components.md for all available components.
Key Components
Layout & Structure
-
Screen
-
Navigation screen with title, loading state, actions
-
Navigator
-
Screen navigation container
-
ScrollView
-
Scrollable content container
-
Section
-
Card-like grouping container
-
Stack
-
Horizontal/vertical layout
-
List
-
Structured data rows
Actions
-
Button
-
Tappable action button
-
Tile
-
Smart grid tile (home screen only)
-
Selectable
-
Make components tappable
Forms
-
TextField , TextArea
-
Text input
-
NumberField
-
Numeric input
-
EmailField
-
Email with validation
-
DateField , DatePicker
-
Date selection
-
RadioButtonList
-
Single selection
-
Stepper
-
Increment/decrement control
-
PinPad
-
Secure PIN entry
Feedback
-
Banner
-
Important messages
-
Dialog
-
Confirmation prompts
-
Badge
-
Status indicators
Media
-
Icon
-
POS icon catalog
-
Image
-
Visual content
-
CameraScanner
-
Barcode/QR scanning
APIs Reference
See references/apis.md for all available APIs.
Accessing APIs
import { 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; }
Core APIs
Cart API - Modify cart contents
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();
Session API - Authentication and session data
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;
Customer API - Customer data access
const { customer } = useApi<'pos.customer-details.block.render'>(); const customerData = customer.getCustomer();
Toast API - Show notifications
const { toast } = useApi<'pos.home.modal.render'>(); toast.show('Item added successfully');
Navigation API - Screen navigation
const { navigation } = useApi<'pos.home.modal.render'>(); navigation.dismiss(); // Close modal navigation.navigate('ScreenName'); // Navigate to screen
Scanner API - Barcode scanning
const { scanner } = useApi<'pos.home.modal.render'>(); const result = await scanner.scanBarcode();
Print API - Receipt printing
const { print } = useApi<'pos.home.modal.render'>(); await print.printDocument(documentContent);
Direct GraphQL API Access
Available for extensions targeting 2025-07 or later (requires POS 10.6.0+).
const 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' }
})
});
Declare required scopes in shopify.app.toml :
[access_scopes] scopes = "read_products,write_products,read_customers"
Development Workflow
Local Development
shopify app dev
Open the Shopify POS app on your device and connect to the development store.
Testing
-
Install app on development store
-
Open Shopify POS app
-
Navigate to smart grid (home) to see tiles
-
Tap tiles to test modals
-
Navigate to relevant screens (products, customers, orders) for block/action targets
Deployment
shopify app deploy
Best Practices
-
Performance First - Extensions run in critical merchant workflows; minimize API calls and computations
-
Offline Consideration - Use Storage API for data that should persist offline
-
Native Feel - Use provided components to match POS design system
-
Error Handling - Always handle API failures gracefully with user feedback
-
Loading States - Show loading indicators during async operations
Storage API for Offline Data
const { 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;
Complete Example: Loyalty Points Extension
// 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> ); }