
Shopify
- 4 installs
- 1 repo stars
- Updated November 15, 2025
- aia-11-hn-mib/mib-mockinterviewaibot
shopify is a Claude Code skill for building Shopify apps, extensions, and themes using the GraphQL/REST Admin APIs, Shopify CLI, Polaris UI, and Liquid templating.
About
This skill guides building on the Shopify platform: apps, checkout and admin extensions, POS extensions, and themes. It uses the GraphQL and REST Admin APIs, Shopify CLI, Polaris UI components, and Liquid templating. A developer uses it when building Shopify apps, customizing checkout, or managing store data via APIs. It documents CLI commands, access scopes, extension types, and when to build an app versus an extension or theme.
- Builds Shopify apps, extensions, and themes
- Uses GraphQL/REST Admin APIs, Shopify CLI, Polaris, and Liquid
- Covers checkout, admin, and POS UI extensions plus webhooks and billing
Shopify by the numbers
- 4 all-time installs (skills.sh)
- Ranked #3,692 of 4,348 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
shopify capabilities & compatibility
Free skill; requires a Shopify partner/store account and API credentials.
- Capabilities
- shopify app development · checkout customization · theme development · api integration
- Use cases
- api development · frontend
- Pricing
- Bring your own API key
- Requires keys
- SHOPIFYAPPACCESSSCOPESOAUTHCREDENTIALS
What shopify says it does
Comprehensive guide for building on Shopify platform: apps, extensions, themes, and API integrations.
GraphQL Admin API** - Primary API for data operations (recommended)
npx skills add https://github.com/aia-11-hn-mib/mib-mockinterviewaibot --skill shopifyAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 4 |
|---|---|
| repo stars | ★ 1 |
| Last updated | November 15, 2025 |
| Repository | aia-11-hn-mib/mib-mockinterviewaibot ↗ |
What it does
Build Shopify apps, extensions, and themes using the Admin GraphQL/REST APIs, Shopify CLI, Polaris, and Liquid.
Who is it for?
Building Shopify apps, checkout/admin/POS extensions, and themes
When should I use this skill?
Building Shopify apps, customizing checkout, developing themes, or managing store data via APIs
What you get
A working Shopify app, extension, or theme integrated with store data.
- Shopify app
- Checkout/admin/POS extension
- Shopify theme
By the numbers
- 3 build targets (apps, extensions, themes)
- 5 extension points (Checkout, Admin, POS, Customer Account, Theme App)
Files
Shopify Development
Comprehensive guide for building on Shopify platform: apps, extensions, themes, and API integrations.
Platform Overview
Core Components:
- Shopify CLI - Development workflow tool
- GraphQL Admin API - Primary API for data operations (recommended)
- REST Admin API - Legacy API (maintenance mode)
- Polaris UI - Design system for consistent interfaces
- Liquid - Template language for themes
Extension Points:
- Checkout UI - Customize checkout experience
- Admin UI - Extend admin dashboard
- POS UI - Point of Sale customization
- Customer Account - Post-purchase pages
- Theme App Extensions - Embedded theme functionality
Quick Start
Prerequisites
# Install Shopify CLI
npm install -g @shopify/cli@latest
# Verify installation
shopify versionCreate New App
# Initialize app
shopify app init
# Start development server
shopify app dev
# Generate extension
shopify app generate extension --type checkout_ui_extension
# Deploy
shopify app deployTheme Development
# Initialize theme
shopify theme init
# Start local preview
shopify theme dev
# Pull from store
shopify theme pull --live
# Push to store
shopify theme push --developmentDevelopment Workflow
1. App Development
Setup:
shopify app init
cd my-appConfigure Access Scopes (shopify.app.toml):
[access_scopes]
scopes = "read_products,write_products,read_orders"Start Development:
shopify app dev # Starts local server with tunnelAdd Extensions:
shopify app generate extension --type checkout_ui_extensionDeploy:
shopify app deploy # Builds and uploads to Shopify2. Extension Development
Available Types:
- Checkout UI -
checkout_ui_extension - Admin Action -
admin_action - Admin Block -
admin_block - POS UI -
pos_ui_extension - Function -
function(discounts, payment, delivery, validation)
Workflow:
shopify app generate extension
# Select type, configure
shopify app dev # Test locally
shopify app deploy # Publish3. Theme Development
Setup:
shopify theme init
# Choose Dawn (reference theme) or start freshLocal Development:
shopify theme dev
# Preview at localhost:9292
# Auto-syncs to development themeDeployment:
shopify theme push --development # Push to dev theme
shopify theme publish --theme=123 # Set as liveWhen to Build What
Build an App When:
- Integrating external services
- Adding functionality across multiple stores
- Building merchant-facing admin tools
- Managing store data programmatically
- Implementing complex business logic
- Charging for functionality
Build an Extension When:
- Customizing checkout flow
- Adding fields/features to admin pages
- Creating POS actions for retail
- Implementing discount/payment/shipping rules
- Extending customer account pages
Build a Theme When:
- Creating custom storefront design
- Building unique shopping experiences
- Customizing product/collection pages
- Implementing brand-specific layouts
- Modifying homepage/content pages
Combination Approach:
App + Theme Extension:
- App handles backend logic and data
- Theme extension provides storefront UI
- Example: Product reviews, wishlists, size guides
Essential Patterns
GraphQL Product Query
query GetProducts($first: Int!) {
products(first: $first) {
edges {
node {
id
title
handle
variants(first: 5) {
edges {
node {
id
price
inventoryQuantity
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}Checkout Extension (React)
import { reactExtension, BlockStack, TextField, Checkbox } from '@shopify/ui-extensions-react/checkout';
export default reactExtension('purchase.checkout.block.render', () => <Extension />);
function Extension() {
const [message, setMessage] = useState('');
return (
<BlockStack>
<TextField label="Gift Message" value={message} onChange={setMessage} />
</BlockStack>
);
}Liquid Product Display
{% for product in collection.products %}
<div class="product-card">
<img src="{{ product.featured_image | img_url: 'medium' }}" alt="{{ product.title }}">
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
<a href="{{ product.url }}">View Details</a>
</div>
{% endfor %}Best Practices
API Usage:
- Prefer GraphQL over REST for new development
- Request only needed fields to reduce costs
- Implement pagination for large datasets
- Use bulk operations for batch processing
- Respect rate limits (cost-based for GraphQL)
Security:
- Store API credentials in environment variables
- Verify webhook signatures
- Use OAuth for public apps
- Request minimal access scopes
- Implement session tokens for embedded apps
Performance:
- Cache API responses when appropriate
- Optimize images in themes
- Minimize Liquid logic complexity
- Use async loading for extensions
- Monitor query costs in GraphQL
Testing:
- Use development stores for testing
- Test across different store plans
- Verify mobile responsiveness
- Check accessibility (keyboard, screen readers)
- Validate GDPR compliance
Reference Documentation
Detailed guides for advanced topics:
- [App Development](references/app-development.md) - OAuth, APIs, webhooks, billing
- [Extensions](references/extensions.md) - Checkout, Admin, POS, Functions
- [Themes](references/themes.md) - Liquid, sections, deployment
Scripts
[shopify_init.py](scripts/shopify_init.py) - Initialize Shopify projects interactively
python scripts/shopify_init.pyTroubleshooting
Rate Limit Errors:
- Monitor
X-Shopify-Shop-Api-Call-Limitheader - Implement exponential backoff
- Use bulk operations for large datasets
Authentication Failures:
- Verify access token validity
- Check required scopes granted
- Ensure OAuth flow completed
Extension Not Appearing:
- Verify extension target correct
- Check extension published
- Ensure app installed on store
Webhook Not Receiving:
- Verify webhook URL accessible
- Check signature validation
- Review logs in Partner Dashboard
Resources
Official Documentation:
- Shopify Docs: https://shopify.dev/docs
- GraphQL API: https://shopify.dev/docs/api/admin-graphql
- Shopify CLI: https://shopify.dev/docs/api/shopify-cli
- Polaris: https://polaris.shopify.com
Tools:
- GraphiQL Explorer (Admin → Settings → Apps → Develop apps)
- Partner Dashboard (app management)
- Development stores (free testing)
API Versioning:
- Quarterly releases (YYYY-MM format)
- Current: 2025-01
- 12-month support per version
- Test before version updates
---
Note: This skill covers Shopify platform as of January 2025. Refer to official documentation for latest updates.
Shopify API Research Documentation
This directory contains comprehensive research and analysis of various APIs for integration purposes.
Contents
Shopify GraphQL Admin API Analysis
File: shopify-graphql-admin-api-analysis.md Date: 2025-10-25 Status: Complete Thoroughness: Very Thorough
A comprehensive analysis of Shopify's GraphQL Admin API covering:
- API overview and capabilities
- Key features and operations
- Common query and mutation patterns
- Best practices and optimization strategies
- Authentication and security considerations
- Rate limiting and performance optimization
- Typical use cases and implementation patterns
- Troubleshooting guide
- Code examples in multiple languages
- Resources and further learning
Key Sections: 1. Executive Summary 2. API Overview 3. Key Features 4. Common Operations (Queries & Mutations) 5. API Structure (Types, Connections, Errors) 6. Best Practices 7. Typical Use Cases 8. API Versions and Deprecation 9. Tools and SDKs 10. Security Considerations 11. Performance Optimization 12. Common Patterns 13. Troubleshooting 14. Resources and Further Learning
Size: 1,348 lines, ~26KB
---
Usage
These documents are intended for:
- Development planning and architecture decisions
- Team onboarding and training
- Integration implementation reference
- Best practices guidance
- Troubleshooting support
---
Maintenance
- Documents should be reviewed quarterly
- Update when API versions change
- Add new findings from implementation experience
- Keep code examples current with latest SDK versions
---
Last Updated: 2025-10-25 Maintained By: Claude Code Engineering Team
Shopify CLI Commands Reference
Comprehensive guide to Shopify CLI for app and theme development.
Table of Contents
1. Installation 2. App Commands 3. Theme Commands 4. Extension Commands 5. Configuration Commands 6. Common Workflows 7. Troubleshooting
Installation
Install Shopify CLI
# Via npm (recommended)
npm install -g @shopify/cli@latest
# Via Homebrew (macOS)
brew tap shopify/shopify
brew install shopify-cli
# Via Ruby Gem (legacy)
gem install shopify-cliVerify Installation
shopify versionUpdate CLI
npm update -g @shopify/cliApp Commands
Initialize New App
shopify app initPrompts:
- App name
- Technology stack (Node, Ruby, PHP, Remix)
- Template selection
Creates:
- App configuration (
shopify.app.toml) - Project structure
- Development dependencies
Start Development Server
shopify app devWhat it does:
- Starts local development server
- Creates tunnel for embedded app testing
- Auto-reloads on file changes
- Provides preview URL
Options:
shopify app dev --reset # Reset cached state
shopify app dev --store=mystore # Specify store
shopify app dev --port=3000 # Custom portDeploy App
shopify app deployWhat it does:
- Builds production assets
- Uploads extensions to Shopify
- Updates app configuration
- Creates new app version
Generate Extension
shopify app generate extensionExtension Types:
- Checkout UI Extension
- Admin UI Extension (app block, app overlay, link)
- POS UI Extension
- Customer Account UI Extension
- Post-Purchase UI Extension
- Shopify Function (discount, payment, delivery, validation)
- Theme App Extension
- Web Pixel Extension
Example:
shopify app generate extension --type checkout_ui_extension --name gift-messageGenerate Schema
shopify app generate schemaGenerates TypeScript types from GraphQL schema.
Build App
shopify app buildCompiles app for production without deploying.
Info
shopify app infoDisplays app configuration and metadata.
Config Link
shopify app config linkLinks local project to app in Partners dashboard.
Config Use
shopify app config useSwitch between multiple app configurations (staging, production).
Config Push
shopify app config pushPush local config to Partners dashboard.
Versions List
shopify app versions listView all deployed app versions.
Theme Commands
Initialize Theme
shopify theme initOptions:
- Clone Dawn (Shopify's reference theme)
- Start from scratch
- Clone existing theme
Pull Theme
shopify theme pullDownload theme files from store.
Options:
shopify theme pull --theme=123456789 # Specific theme ID
shopify theme pull --live # Pull live theme
shopify theme pull --development # Pull development theme
shopify theme pull --only=templates # Specific directory
shopify theme pull --ignore=config/* # Ignore patternsPush Theme
shopify theme pushUpload local theme files to store.
Options:
shopify theme push --theme=123456789 # Push to specific theme
shopify theme push --live # Push to live theme (dangerous!)
shopify theme push --development # Push to development theme
shopify theme push --unpublished # Create new unpublished theme
shopify theme push --json # Only push JSON files
shopify theme push --allow-live # Allow pushing to live (confirmation)Theme Dev
shopify theme devStart local theme development server with hot reload.
Options:
shopify theme dev --theme=123456789 # Connect to specific theme
shopify theme dev --store=mystore # Specific store
shopify theme dev --host=0.0.0.0 # Custom host
shopify theme dev --port=9292 # Custom port
shopify theme dev --poll # Use polling for file changesFeatures:
- Live reload on file changes
- Local preview at
http://localhost:9292 - Syncs changes to development theme
- Hot Module Replacement (HMR)
Theme Check
shopify theme checkLints theme code for best practices and errors.
Options:
shopify theme check --list # List all checks
shopify theme check --category # Check specific category
shopify theme check --auto-correct # Fix issues automaticallyTheme Share
shopify theme shareGenerate shareable preview link for unpublished theme.
Theme Publish
shopify theme publish --theme=123456789Set theme as live on store.
Theme Package
shopify theme packageCreate .zip file for theme upload or distribution.
Theme List
shopify theme listDisplay all themes on connected store.
Theme Delete
shopify theme delete --theme=123456789Remove theme from store.
Extension Commands
Extension Build
shopify extension buildCompile extension for production.
Extension Check
shopify extension checkValidate extension configuration and code.
Extension Push
shopify extension pushUpload extension to Shopify.
Configuration Commands
Login
shopify loginAuthenticate with Shopify Partners account.
Options:
shopify login --store=mystore # Login to specific storeLogout
shopify logoutRemove stored authentication credentials.
Whoami
shopify whoamiDisplay current authentication status.
Store
shopify storeDisplay current connected store.
Switch Store
shopify store switchChange connected development store.
Common Workflows
New App Development
1. Create app:
shopify app init
cd my-app2. Start development:
shopify app dev3. Generate extensions:
shopify app generate extension --type checkout_ui_extension4. Deploy:
shopify app deployTheme Development
1. Pull existing theme:
shopify theme pull --live2. Start local development:
shopify theme dev3. Make changes and test:
- Edit files in editor
- See changes at
localhost:9292
4. Push to development theme:
shopify theme push --development5. Publish when ready:
shopify theme publish --theme=123456789Working with Multiple Environments
1. Create app config file per environment:
# Development config
shopify app config use dev
# Staging config
shopify app config use staging
# Production config
shopify app config use production2. Switch between configs:
shopify app config use dev
shopify app dev
shopify app config use production
shopify app deployExtension Development
1. Generate extension:
shopify app generate extension2. Select type and configure
3. Develop locally:
shopify app dev4. Test in development store
5. Deploy:
shopify app deployEnvironment Variables
Required Variables
For Apps:
SHOPIFY_API_KEY=your_api_key
SHOPIFY_API_SECRET=your_api_secret
SCOPES=read_products,write_orders
HOST=https://your-domain.comFor Themes:
SHOPIFY_CLI_THEME_TOKEN=shptka_xxx
SHOPIFY_FLAG_STORE=mystore.myshopify.com.env File Example
# App configuration
SHOPIFY_API_KEY=abc123def456
SHOPIFY_API_SECRET=xyz789uvw012
SCOPES=read_products,write_products,read_orders
# Database
DATABASE_URL=postgresql://localhost/myapp
# Other
NODE_ENV=development
PORT=3000Flags and Options
Global Flags
--help, -h # Show help
--version, -v # Show version
--verbose # Show detailed output
--path # Specify project directory
--no-color # Disable colored outputApp-Specific Flags
--reset # Reset local state
--store # Target store
--config # Config file path
--subscription-product-url # Specify subscription URLTheme-Specific Flags
--theme # Theme ID
--live # Target live theme
--development # Target development theme
--unpublished # Create unpublished theme
--nodelete # Don't delete files on remote
--only # Include only specified paths
--ignore # Exclude specified paths
--json # JSON output formatConfiguration Files
shopify.app.toml
Main app configuration file.
# Basic info
name = "my-app"
client_id = "abc123"
application_url = "https://my-app.com"
embedded = true
# Build configuration
[build]
automatically_update_urls_on_dev = true
dev_store_url = "my-dev-store.myshopify.com"
# Access scopes
[access_scopes]
scopes = "read_products,write_products,read_orders"
# Webhooks
[webhooks]
api_version = "2025-01"
[[webhooks.subscriptions]]
topics = ["orders/create"]
uri = "/webhooks/orders/create"
[[webhooks.subscriptions]]
topics = ["app/uninstalled"]
uri = "/webhooks/app/uninstalled"
# App proxy
[app_proxy]
url = "https://my-app.com/proxy"
subpath = "apps/my-app"
prefix = "apps"
# GDPR webhooks (mandatory)
[webhooks.privacy_compliance]
customer_data_request_url = "/webhooks/gdpr/data-request"
customer_deletion_url = "/webhooks/gdpr/customer-deletion"
shop_deletion_url = "/webhooks/gdpr/shop-deletion"shopify.extension.toml
Extension configuration.
name = "gift-message"
type = "checkout_ui_extension"
handle = "gift-message"
[extension_points]
api_version = "2025-01"
[[extension_points.targets]]
target = "purchase.checkout.block.render"
[capabilities]
network_access = true
block_progress = falseTroubleshooting
Common Issues
1. Authentication Errors
# Re-authenticate
shopify logout
shopify login2. Port Already in Use
# Use different port
shopify app dev --port=30013. Theme Not Syncing
# Reset and restart
shopify theme dev --reset4. Extension Not Appearing
# Rebuild and redeploy
shopify extension build
shopify app deploy5. Config Issues
# Validate config
shopify app info
# Relink config
shopify app config link6. Build Failures
# Clear cache and rebuild
rm -rf node_modules
npm install
shopify app buildDebug Mode
Enable verbose logging:
SHOPIFY_CLI_STACKTRACE=1 shopify app dev --verboseUpdate CLI
Many issues resolved by updating:
npm update -g @shopify/cli@latestCheck CLI Status
shopify statusPerformance Tips
Faster Development
# Only sync specific directories
shopify theme dev --only=sections,snippets
# Ignore large directories
shopify theme dev --ignore=assets/videosFaster Deployments
# Deploy only changed files
shopify theme push --only=changed
# Parallel uploads
shopify theme push --concurrent=10Best Practices
1. Use version control: Commit shopify.app.toml and extension configs 2. Environment separation: Use different configs for dev/staging/prod 3. Ignore build artifacts: Add to .gitignore:
.shopify/
dist/
build/
node_modules/
.env4. Regular updates: Keep CLI updated for bug fixes and features 5. Development stores: Use dedicated development stores for testing 6. Backup before pushing: Always pull before pushing themes to avoid conflicts 7. Test extensions thoroughly: Use development stores before production deployment
Official Resources
- CLI Documentation: https://shopify.dev/docs/api/shopify-cli
- CLI GitHub: https://github.com/Shopify/cli
- App Configuration: https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration
- Theme Development: https://shopify.dev/docs/themes/tools/cli
GraphQL Admin API Reference
Comprehensive guide to Shopify's GraphQL Admin API for building apps and integrations.
Table of Contents
1. Overview 2. Authentication 3. API Structure 4. Common Resources 5. Queries 6. Mutations 7. Bulk Operations 8. Pagination 9. Rate Limiting 10. Error Handling 11. Best Practices
Overview
The GraphQL Admin API is Shopify's recommended API for all new development. It provides efficient, type-safe access to store data with flexible querying capabilities.
Key Advantages:
- Request only the data you need (no over-fetching)
- Fetch related resources in a single request (no under-fetching)
- Strong typing with introspection
- Predictable responses
- Future-proof with evolving schema
Endpoint:
POST https://{shop-name}.myshopify.com/admin/api/2025-01/graphql.jsonAuthentication
Access Token Header
{
'X-Shopify-Access-Token': 'your-access-token',
'Content-Type': 'application/json'
}Request Format
fetch(`https://${shop}.myshopify.com/admin/api/2025-01/graphql.json`, {
method: 'POST',
headers: {
'X-Shopify-Access-Token': accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: '...',
variables: { ... }
})
})API Structure
Schema Exploration
Use GraphiQL in Shopify admin (Settings → Apps and sales channels → Develop apps → API credentials → Admin API → Explore with GraphiQL)
Query Structure
query QueryName($variable: Type!) {
resource(first: 10, query: $variable) {
edges {
node {
id
field1
field2
}
}
pageInfo {
hasNextPage
endCursor
}
}
}Mutation Structure
mutation MutationName($input: ResourceInput!) {
resourceCreate(input: $input) {
resource {
id
field
}
userErrors {
field
message
}
}
}Common Resources
Products
Manage product catalog and inventory.
Fields:
id- Global ID (gid://shopify/Product/123)title- Product namehandle- URL-friendly identifierdescription- Product description (HTML)productType- Product categoryvendor- Product brand/suppliertags- Search/filter tagsstatus- ACTIVE, ARCHIVED, DRAFTvariants- Product variations (size, color, etc.)images- Product imagespriceRangeV2- Min/max pricingtotalInventory- Total stock across locations
Orders
Access and manage customer orders.
Fields:
id- Global IDname- Order number (#1001)createdAt- Order timestampcustomer- Customer detailslineItems- Ordered productstotalPriceSet- Order total with currencydisplayFinancialStatus- PAID, PENDING, REFUNDEDdisplayFulfillmentStatus- FULFILLED, UNFULFILLED, PARTIALshippingAddress- Delivery addressbillingAddress- Billing address
Customers
Manage customer accounts and data.
Fields:
id- Global IDemail- Customer emailfirstName,lastName- Customer namephone- Contact numberaddresses- Saved addressesorders- Order historylifetimeDuration- Account ageamountSpent- Total purchasestags- Customer segmentation
Inventory
Track product stock across locations.
Resources:
InventoryLevel- Stock at specific locationInventoryItem- Inventory entity for variantLocation- Store/warehouse location
Queries
Fetch Products
query GetProducts($first: Int!, $query: String) {
products(first: $first, query: $query) {
edges {
node {
id
title
handle
status
productType
vendor
priceRangeV2 {
minVariantPrice {
amount
currencyCode
}
}
variants(first: 5) {
edges {
node {
id
title
price
sku
inventoryQuantity
}
}
}
images(first: 1) {
edges {
node {
url
altText
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}Variables:
{
"first": 10,
"query": "status:active product_type:Clothing"
}Fetch Single Product
query GetProduct($id: ID!) {
product(id: $id) {
id
title
description
variants(first: 10) {
edges {
node {
id
title
price
compareAtPrice
sku
barcode
inventoryQuantity
weight
weightUnit
}
}
}
}
}Fetch Orders
query GetOrders($first: Int!, $query: String) {
orders(first: $first, query: $query) {
edges {
node {
id
name
createdAt
displayFinancialStatus
displayFulfillmentStatus
totalPriceSet {
shopMoney {
amount
currencyCode
}
}
customer {
id
email
firstName
lastName
}
lineItems(first: 10) {
edges {
node {
id
title
quantity
originalUnitPriceSet {
shopMoney {
amount
currencyCode
}
}
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}Fetch Customers
query GetCustomers($first: Int!, $query: String) {
customers(first: $first, query: $query) {
edges {
node {
id
email
firstName
lastName
phone
ordersCount
amountSpent {
amount
currencyCode
}
tags
addresses {
address1
city
province
country
zip
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}Fetch Inventory Levels
query GetInventoryLevels($first: Int!, $inventoryItemId: ID!) {
inventoryItem(id: $inventoryItemId) {
id
sku
inventoryLevels(first: $first) {
edges {
node {
id
available
location {
id
name
}
}
}
}
}
}Mutations
Create Product
mutation CreateProduct($input: ProductInput!) {
productCreate(input: $input) {
product {
id
title
handle
status
}
userErrors {
field
message
}
}
}Variables:
{
"input": {
"title": "New Product",
"productType": "Clothing",
"vendor": "Brand Name",
"status": "ACTIVE",
"variants": [
{
"price": "29.99",
"sku": "SKU-001",
"inventoryPolicy": "DENY",
"inventoryQuantity": 100
}
]
}
}Update Product
mutation UpdateProduct($input: ProductInput!) {
productUpdate(input: $input) {
product {
id
title
status
}
userErrors {
field
message
}
}
}Variables:
{
"input": {
"id": "gid://shopify/Product/123",
"title": "Updated Product Title",
"status": "ACTIVE"
}
}Delete Product
mutation DeleteProduct($input: ProductDeleteInput!) {
productDelete(input: $input) {
deletedProductId
userErrors {
field
message
}
}
}Create Order
mutation CreateOrder($input: DraftOrderInput!) {
draftOrderCreate(input: $input) {
draftOrder {
id
name
totalPrice
}
userErrors {
field
message
}
}
}Update Inventory
mutation UpdateInventory($input: InventoryAdjustQuantitiesInput!) {
inventoryAdjustQuantities(input: $input) {
inventoryAdjustmentGroup {
id
reason
}
userErrors {
field
message
}
}
}Create Customer
mutation CreateCustomer($input: CustomerInput!) {
customerCreate(input: $input) {
customer {
id
email
firstName
lastName
}
userErrors {
field
message
}
}
}Variables:
{
"input": {
"email": "customer@example.com",
"firstName": "John",
"lastName": "Doe",
"phone": "+1234567890",
"acceptsMarketing": true
}
}Bulk Operations
For processing large datasets efficiently.
Start Bulk Query
mutation {
bulkOperationRunQuery(
query: """
{
products {
edges {
node {
id
title
variants {
edges {
node {
id
price
}
}
}
}
}
}
}
"""
) {
bulkOperation {
id
status
}
userErrors {
field
message
}
}
}Check Bulk Operation Status
query {
currentBulkOperation {
id
status
errorCode
createdAt
completedAt
objectCount
fileSize
url
}
}Status Values:
CREATED- Operation createdRUNNING- ProcessingCOMPLETED- Finished successfullyFAILED- Error occurred
Pagination
GraphQL uses cursor-based pagination.
Forward Pagination
query {
products(first: 10, after: "cursor_value") {
edges {
cursor
node {
id
title
}
}
pageInfo {
hasNextPage
endCursor
}
}
}Backward Pagination
query {
products(last: 10, before: "cursor_value") {
edges {
cursor
node {
id
title
}
}
pageInfo {
hasPreviousPage
startCursor
}
}
}Pagination Pattern
let hasNextPage = true;
let cursor = null;
const allProducts = [];
while (hasNextPage) {
const response = await fetchProducts(cursor);
allProducts.push(...response.data.products.edges);
hasNextPage = response.data.products.pageInfo.hasNextPage;
cursor = response.data.products.pageInfo.endCursor;
}Rate Limiting
GraphQL uses cost-based rate limiting.
Query Cost
Each query has a calculated cost based on:
- Number of fields requested
- Number of connections traversed
- Depth of nested queries
Cost Limits:
- Available points: 2000
- Restore rate: 100 points/second
- Maximum query cost: 2000 points
Check Query Cost
query {
products(first: 10) {
edges {
node {
id
title
}
}
}
}
# Response includes:
{
"extensions": {
"cost": {
"requestedQueryCost": 12,
"actualQueryCost": 12,
"throttleStatus": {
"maximumAvailable": 2000,
"currentlyAvailable": 1988,
"restoreRate": 100
}
}
}
}Handle Rate Limits
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function makeRequest(query) {
const response = await fetch(endpoint, {
method: 'POST',
headers: headers,
body: JSON.stringify({ query })
});
const data = await response.json();
if (data.errors?.some(e => e.message.includes('Throttled'))) {
await delay(1000); // Wait 1 second
return makeRequest(query); // Retry
}
return data;
}Error Handling
UserErrors vs System Errors
UserErrors: Business logic validation failures (returned in response data)
{
"data": {
"productCreate": {
"product": null,
"userErrors": [
{
"field": ["title"],
"message": "Title can't be blank"
}
]
}
}
}System Errors: Technical failures (returned in errors array)
{
"errors": [
{
"message": "Field 'invalid' doesn't exist on type 'Product'",
"locations": [{"line": 3, "column": 5}]
}
]
}Error Handling Pattern
async function createProduct(input) {
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: headers,
body: JSON.stringify({
query: CREATE_PRODUCT_MUTATION,
variables: { input }
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
// Check for GraphQL errors
if (data.errors) {
console.error('GraphQL errors:', data.errors);
throw new Error('GraphQL query failed');
}
// Check for user errors
if (data.data.productCreate.userErrors.length > 0) {
console.error('Validation errors:', data.data.productCreate.userErrors);
return { success: false, errors: data.data.productCreate.userErrors };
}
return { success: true, product: data.data.productCreate.product };
} catch (error) {
console.error('Request failed:', error);
throw error;
}
}Best Practices
1. Request Only What You Need
# ❌ Bad: Requesting unnecessary fields
query {
products(first: 10) {
edges {
node {
id
title
description
descriptionHtml
productType
vendor
tags
# ... many more fields
}
}
}
}
# ✅ Good: Request only required fields
query {
products(first: 10) {
edges {
node {
id
title
priceRangeV2 {
minVariantPrice {
amount
}
}
}
}
}
}2. Use Fragments for Reusable Fields
fragment ProductFields on Product {
id
title
handle
priceRangeV2 {
minVariantPrice {
amount
currencyCode
}
}
}
query {
products(first: 10) {
edges {
node {
...ProductFields
}
}
}
}3. Use Variables for Dynamic Queries
# ✅ Good: Using variables
query GetProduct($id: ID!) {
product(id: $id) {
title
}
}
# ❌ Bad: Hardcoded values
query {
product(id: "gid://shopify/Product/123") {
title
}
}4. Implement Exponential Backoff
async function fetchWithBackoff(query, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: headers,
body: JSON.stringify({ query })
});
if (response.status === 429) {
const waitTime = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
await delay(waitTime);
continue;
}
return await response.json();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
}
}
}5. Use Bulk Operations for Large Datasets
For datasets > 1000 records, use bulk operations instead of pagination.
6. Cache Responses
Cache API responses when appropriate to reduce API calls:
const cache = new Map();
async function fetchProductWithCache(id) {
if (cache.has(id)) {
return cache.get(id);
}
const product = await fetchProduct(id);
cache.set(id, product);
// Expire after 5 minutes
setTimeout(() => cache.delete(id), 5 * 60 * 1000);
return product;
}7. Monitor API Usage
Track query costs and adjust as needed:
function logQueryCost(response) {
const cost = response.extensions?.cost;
if (cost) {
console.log(`Query cost: ${cost.actualQueryCost}/${cost.throttleStatus.maximumAvailable}`);
}
}API Versioning
Shopify uses quarterly versioning (YYYY-MM):
- Current stable: 2025-01
- Each version supported for 12 months
- Test breaking changes before version updates
- Use specific version in endpoint URL
Official Resources
- GraphQL API Reference: https://shopify.dev/docs/api/admin-graphql
- GraphiQL Explorer: Admin → Settings → Apps and sales channels → Develop apps
- API Changelog: https://shopify.dev/changelog
- Rate Limiting: https://shopify.dev/docs/api/usage/rate-limits
UI Extensions Reference
Comprehensive guide to building UI extensions for Shopify.
Table of Contents
1. Overview 2. Checkout UI Extensions 3. Admin UI Extensions 4. POS UI Extensions 5. Customer Account UI Extensions 6. Post-Purchase Extensions 7. Common Components 8. Extension APIs 9. Best Practices
Overview
UI Extensions allow you to customize and extend Shopify surfaces without modifying core code. Extensions render natively for optimal performance and use declarative component APIs.
Key Benefits:
- Native rendering (fast performance)
- Consistent UX across Shopify
- Automatic updates and maintenance
- Secure and sandboxed execution
- Mobile and desktop support
Checkout UI Extensions
Customize the checkout and thank-you pages.
Extension Points
Static Targets (Fixed Placement):
purchase.checkout.header.render-after- Below headerpurchase.checkout.contact.render-before- Above contact infopurchase.checkout.shipping-option-list.render-after- Below shipping methodspurchase.checkout.payment-method-list.render-after- Below payment methodspurchase.checkout.footer.render-before- Above footerpurchase.thank-you.header.render-after- Thank you page headerpurchase.thank-you.footer.render-before- Thank you page footer
Block Targets (Flexible Placement):
purchase.checkout.block.render- Merchant-controlled placementpurchase.thank-you.block.render- Thank you page blocks
Setup
1. Generate Extension:
shopify app generate extension --type checkout_ui_extension2. Configuration (`shopify.extension.toml`):
api_version = "2025-01"
[[extensions]]
type = "ui_extension"
name = "gift-message"
handle = "gift-message"
[[extensions.targeting]]
target = "purchase.checkout.block.render"
[capabilities]
network_access = true
api_access = true3. Entry Point (`src/Checkout.jsx`):
import React, { useState } from 'react';
import {
reactExtension,
BlockStack,
TextField,
Checkbox,
Banner,
useApi
} from '@shopify/ui-extensions-react/checkout';
export default reactExtension(
'purchase.checkout.block.render',
() => <Extension />
);
function Extension() {
const { extensionPoint } = useApi();
const [message, setMessage] = useState('');
const [isGift, setIsGift] = useState(false);
return (
<BlockStack spacing="loose">
<Banner title="Gift Options" />
<Checkbox
checked={isGift}
onChange={setIsGift}
>
This is a gift
</Checkbox>
{isGift && (
<TextField
label="Gift Message"
value={message}
onChange={setMessage}
multiline={3}
/>
)}
</BlockStack>
);
}Checkout Components
Layout
View- Container componentBlockStack- Vertical stackingInlineStack- Horizontal stackingInlineLayout- Responsive inline layoutBlockLayout- Responsive block layoutGrid- Grid layoutGridItem- Grid cellDivider- Visual separatorScrollView- Scrollable container
Input
TextField- Text inputCheckbox- Boolean selectionSelect- Dropdown selectionDatePicker- Date inputForm- Form container
Display
Text- TypographyHeading- Section headersBanner- Important messagesBadge- Status indicatorsImage- ImagesIcon- IconsLink- HyperlinksList- Ordered/unordered listsListItem- List items
Interactive
Button- Primary actionsPressable- Custom clickable areasModal- Overlay dialogsPopover- Contextual overlays
Loading
Spinner- Loading indicatorSkeletonText- Text placeholderSkeletonImage- Image placeholder
Checkout APIs
useApi Hook
import { useApi } from '@shopify/ui-extensions-react/checkout';
function Extension() {
const {
extensionPoint, // Current extension point
shop, // Shop details
storefront, // Storefront API client
i18n, // Internationalization
sessionToken // Session token for auth
} = useApi();
}Cart Data
import {
useCartLines,
useApplyCartLinesChange
} from '@shopify/ui-extensions-react/checkout';
function Extension() {
const lines = useCartLines();
const applyChange = useApplyCartLinesChange();
async function updateQuantity(lineId, quantity) {
await applyChange({
type: 'updateCartLine',
id: lineId,
quantity: quantity
});
}
return lines.map(line => (
<Text key={line.id}>
{line.merchandise.product.title} - Qty: {line.quantity}
</Text>
));
}Shipping Address
import {
useShippingAddress
} from '@shopify/ui-extensions-react/checkout';
function Extension() {
const address = useShippingAddress();
return (
<Text>
Shipping to: {address.city}, {address.countryCode}
</Text>
);
}Metafields
import { useMetafields } from '@shopify/ui-extensions-react/checkout';
function Extension() {
const metafields = useMetafields();
const customData = metafields.find(
m => m.namespace === 'custom' && m.key === 'data'
);
return <Text>{customData?.value}</Text>;
}Attributes
import {
useAttributes,
useApplyAttributeChange
} from '@shopify/ui-extensions-react/checkout';
function Extension() {
const attributes = useAttributes();
const applyChange = useApplyAttributeChange();
async function saveGiftMessage(message) {
await applyChange({
type: 'updateAttribute',
key: 'gift_message',
value: message
});
}
}Admin UI Extensions
Extend Shopify admin interface.
Extension Types
1. Admin Action
Custom actions on resource pages (products, orders, customers).
Generate:
shopify app generate extension --type admin_actionConfig:
[[extensions.targeting]]
module = "Admin::Product::SubscriptionExtension"
target = "admin.product-details.action.render"Example:
import {
reactExtension,
AdminAction,
Button
} from '@shopify/ui-extensions-react/admin';
export default reactExtension(
'admin.product-details.action.render',
() => <Extension />
);
function Extension() {
async function handleExport() {
// Custom export logic
console.log('Exporting product...');
}
return (
<AdminAction
title="Export Product"
primaryAction={
<Button onPress={handleExport}>Export</Button>
}
/>
);
}2. Admin Block
Embedded content in admin pages.
Targets:
admin.product-details.block.renderadmin.order-details.block.renderadmin.customer-details.block.render
Example:
import {
reactExtension,
BlockStack,
Text,
Badge,
useData
} from '@shopify/ui-extensions-react/admin';
export default reactExtension(
'admin.product-details.block.render',
() => <Extension />
);
function Extension() {
const { data } = useData();
const product = data.product;
return (
<BlockStack>
<Text variant="headingMd">Custom Analytics</Text>
<Text>Views: {product.viewCount || 0}</Text>
<Badge tone="success">Popular</Badge>
</BlockStack>
);
}Admin Components
AdminAction- Action containerAdminBlock- Block containerBlockStack- Vertical layoutInlineStack- Horizontal layoutButton- ActionsText- TypographyBadge- StatusBanner- AlertsTextField- InputSelect- DropdownCheckbox- Boolean input
POS UI Extensions
Customize Point of Sale experience.
Extension Types
1. Smart Grid Tile
Quick access action tile.
Generate:
shopify app generate extension --type pos_ui_extensionExample:
import {
reactExtension,
SmartGridTile,
Text
} from '@shopify/ui-extensions-react/pos';
export default reactExtension(
'pos.home.tile.render',
() => <Extension />
);
function Extension() {
function handlePress() {
// Open custom workflow
}
return (
<SmartGridTile
title="Gift Cards"
subtitle="Manage gift cards"
onPress={handlePress}
/>
);
}2. Modal Action
Full-screen modal for complex workflows.
Example:
import {
reactExtension,
Screen,
BlockStack,
Button,
TextField,
useApi
} from '@shopify/ui-extensions-react/pos';
export default reactExtension(
'pos.home.modal.render',
() => <Extension />
);
function Extension() {
const { navigation } = useApi();
function handleSave() {
// Save logic
navigation.pop();
}
return (
<Screen name="Gift Card" title="Gift Card Management">
<BlockStack>
<TextField label="Amount" />
<TextField label="Recipient Email" />
<Button onPress={handleSave}>Issue Gift Card</Button>
</BlockStack>
</Screen>
);
}POS Components
Screen- Full-screen containerSmartGridTile- Grid tileBlockStack- Vertical layoutInlineStack- Horizontal layoutButton- ActionsTextField- InputList- ListsText- Typography
Customer Account UI Extensions
Customize customer account pages.
Targets
customer-account.order-status.block.render- Order status pagecustomer-account.order-index.block.render- Order list pagecustomer-account.profile.block.render- Profile page
Example
import {
reactExtension,
BlockStack,
Text,
Button,
useApi
} 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 process
}
return (
<BlockStack>
<Text variant="headingMd">Need to return?</Text>
<Text>Start a return for order {order.name}</Text>
<Button onPress={handleReturn}>Start Return</Button>
</BlockStack>
);
}Post-Purchase Extensions
Upsell offers on thank-you page.
Target
purchase.thank-you.block.render
Example
import {
reactExtension,
BlockStack,
Text,
Button,
Image,
useApi,
useCartLines
} from '@shopify/ui-extensions-react/checkout';
export default reactExtension(
'purchase.thank-you.block.render',
() => <Extension />
);
function Extension() {
const { applyCartLinesChange } = useApi();
const lines = useCartLines();
async function addUpsellProduct() {
await applyCartLinesChange({
type: 'addCartLine',
merchandiseId: 'gid://shopify/ProductVariant/123',
quantity: 1
});
}
return (
<BlockStack spacing="loose">
<Text variant="headingMd">Complete Your Order</Text>
<Image source="https://cdn.shopify.com/..." />
<Text>Add this matching accessory for 20% off!</Text>
<Button onPress={addUpsellProduct}>Add to Order</Button>
</BlockStack>
);
}Common Components
BlockStack
Vertical stacking with spacing control.
<BlockStack spacing="loose">
<Text>Item 1</Text>
<Text>Item 2</Text>
<Text>Item 3</Text>
</BlockStack>Props:
spacing:"none"|"extraTight"|"tight"|"base"|"loose"|"extraLoose"alignment:"leading"|"center"|"trailing"
InlineStack
Horizontal stacking.
<InlineStack spacing="base" alignment="center">
<Button>Cancel</Button>
<Button kind="primary">Save</Button>
</InlineStack>TextField
Text input field.
<TextField
label="Email Address"
value={email}
onChange={setEmail}
type="email"
required
error={emailError}
/>Props:
label: Label textvalue: Current valueonChange: Change handlertype:"text"|"email"|"number"|"tel"|"url"multiline: Number of rowsrequired: Required fielddisabled: Disabled stateerror: Error message
Button
Action button.
<Button
kind="primary"
onPress={handleSubmit}
disabled={!isValid}
loading={isSubmitting}
>
Submit Order
</Button>Props:
kind:"primary"|"secondary"|"plain"onPress: Click handlerdisabled: Disabled stateloading: Loading state
Banner
Message banner.
<Banner status="warning" title="Important">
Your order will be delayed due to weather conditions.
</Banner>Props:
status:"info"|"success"|"warning"|"critical"title: Banner title
Checkbox
Boolean input.
<Checkbox
checked={agreedToTerms}
onChange={setAgreedToTerms}
>
I agree to the terms and conditions
</Checkbox>Modal
Overlay dialog.
<Modal
open={isOpen}
onClose={handleClose}
title="Confirmation"
>
<BlockStack>
<Text>Are you sure?</Text>
<InlineStack>
<Button onPress={handleClose}>Cancel</Button>
<Button kind="primary" onPress={handleConfirm}>Confirm</Button>
</InlineStack>
</BlockStack>
</Modal>Extension APIs
Network Requests
Extensions can make network requests to your app backend.
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();
}
}Storefront API
Query storefront data.
import { useApi } from '@shopify/ui-extensions-react/checkout';
function Extension() {
const { storefront } = useApi();
async function fetchProducts() {
const { data } = await storefront.query(`
query {
products(first: 10) {
edges {
node {
id
title
priceRange {
minVariantPrice {
amount
}
}
}
}
}
}
`);
return data.products.edges;
}
}Analytics
Track custom events.
import { useApi } from '@shopify/ui-extensions-react/checkout';
function Extension() {
const { analytics } = useApi();
function trackEvent() {
analytics.publish('custom_event', {
customProperty: 'value'
});
}
}Best Practices
Performance
1. Lazy Load Data:
import { useEffect, useState } from 'react';
function Extension() {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
if (!data) return <Spinner />;
return <Display data={data} />;
}2. Memoize Expensive Computations:
import { useMemo } from 'react';
function Extension() {
const lines = useCartLines();
const total = useMemo(() => {
return lines.reduce((sum, line) => {
return sum + (parseFloat(line.cost.totalAmount.amount) * line.quantity);
}, 0);
}, [lines]);
}User Experience
1. Provide Loading States:
{isLoading ? <Spinner /> : <Content />}2. Show Error Messages:
{error && <Banner status="critical">{error}</Banner>}3. Validate Input:
<TextField
label="Email"
value={email}
onChange={setEmail}
error={!isValidEmail(email) ? 'Invalid email' : undefined}
/>Security
1. Verify Session Tokens:
const token = await sessionToken.get();
// Always send token to your backend for verification2. Sanitize User Input:
const sanitized = input.trim().replace(/[<>]/g, '');3. Use HTTPS: All network requests must use HTTPS.
Testing
Local Testing
1. Start dev server:
shopify app dev2. Install on development store
3. Navigate to checkout/admin page
4. Verify extension appears and functions
Manual Testing Checklist
- [ ] Extension loads correctly
- [ ] All components render properly
- [ ] Form validation works
- [ ] Network requests succeed
- [ ] Error states display correctly
- [ ] Loading states show appropriately
- [ ] Mobile responsive
- [ ] Desktop layout correct
- [ ] Accessibility (keyboard navigation, screen readers)
Deployment
1. Build extension:
shopify extension build2. Deploy app:
shopify app deploy3. Test in production store
4. Monitor for errors
Official Resources
- Checkout Extensions: https://shopify.dev/docs/api/checkout-extensions
- Admin Extensions: https://shopify.dev/docs/apps/admin/extensions
- POS Extensions: https://shopify.dev/docs/apps/pos/extensions
- Component Reference: https://shopify.dev/docs/api/checkout-ui-extensions/components
- Best Practices: https://shopify.dev/docs/apps/best-practices/performance
App Development Reference
Guide for building Shopify apps with OAuth, GraphQL/REST APIs, webhooks, and billing.
OAuth Authentication
OAuth 2.0 Flow
1. Redirect to Authorization URL:
https://{shop}.myshopify.com/admin/oauth/authorize?
client_id={api_key}&
scope={scopes}&
redirect_uri={redirect_uri}&
state={nonce}2. Handle Callback:
app.get('/auth/callback', async (req, res) => {
const { code, shop, state } = req.query;
// Verify state to prevent CSRF
if (state !== storedState) {
return res.status(403).send('Invalid state');
}
// Exchange code for access token
const accessToken = await exchangeCodeForToken(shop, code);
// Store token securely
await storeAccessToken(shop, accessToken);
res.redirect(`https://${shop}/admin/apps/${appHandle}`);
});3. Exchange Code for Token:
async function exchangeCodeForToken(shop, code) {
const response = await fetch(`https://${shop}/admin/oauth/access_token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: process.env.SHOPIFY_API_KEY,
client_secret: process.env.SHOPIFY_API_SECRET,
code
})
});
const { access_token } = await response.json();
return access_token;
}Access Scopes
Common Scopes:
read_products,write_products- Product catalogread_orders,write_orders- Order managementread_customers,write_customers- Customer dataread_inventory,write_inventory- Stock levelsread_fulfillments,write_fulfillments- Order fulfillmentread_shipping,write_shipping- Shipping ratesread_analytics- Store analyticsread_checkouts,write_checkouts- Checkout data
Full list: https://shopify.dev/api/usage/access-scopes
Session Tokens (Embedded Apps)
For embedded apps using App Bridge:
import { getSessionToken } from '@shopify/app-bridge/utilities';
async function authenticatedFetch(url, options = {}) {
const app = createApp({ ... });
const token = await getSessionToken(app);
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`
}
});
}GraphQL Admin API
Making Requests
async function graphqlRequest(shop, accessToken, query, variables = {}) {
const response = await fetch(
`https://${shop}/admin/api/2025-01/graphql.json`,
{
method: 'POST',
headers: {
'X-Shopify-Access-Token': accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ query, variables })
}
);
const data = await response.json();
if (data.errors) {
throw new Error(`GraphQL errors: ${JSON.stringify(data.errors)}`);
}
return data.data;
}Product Operations
Create Product:
mutation CreateProduct($input: ProductInput!) {
productCreate(input: $input) {
product {
id
title
handle
}
userErrors {
field
message
}
}
}Variables:
{
"input": {
"title": "New Product",
"productType": "Apparel",
"vendor": "Brand",
"status": "ACTIVE",
"variants": [
{ "price": "29.99", "sku": "SKU-001", "inventoryQuantity": 100 }
]
}
}Update Product:
mutation UpdateProduct($input: ProductInput!) {
productUpdate(input: $input) {
product { id title }
userErrors { field message }
}
}Query Products:
query GetProducts($first: Int!, $query: String) {
products(first: $first, query: $query) {
edges {
node {
id
title
status
variants(first: 5) {
edges {
node { id price inventoryQuantity }
}
}
}
}
pageInfo { hasNextPage endCursor }
}
}Order Operations
Query Orders:
query GetOrders($first: Int!) {
orders(first: $first) {
edges {
node {
id
name
createdAt
displayFinancialStatus
totalPriceSet {
shopMoney { amount currencyCode }
}
customer { email firstName lastName }
}
}
}
}Fulfill Order:
mutation FulfillOrder($input: FulfillmentInput!) {
fulfillmentCreate(input: $input) {
fulfillment { id status trackingInfo { number url } }
userErrors { field message }
}
}Webhooks
Configuration
In shopify.app.toml:
[webhooks]
api_version = "2025-01"
[[webhooks.subscriptions]]
topics = ["orders/create"]
uri = "/webhooks/orders/create"
[[webhooks.subscriptions]]
topics = ["products/update"]
uri = "/webhooks/products/update"
[[webhooks.subscriptions]]
topics = ["app/uninstalled"]
uri = "/webhooks/app/uninstalled"
# GDPR mandatory webhooks
[webhooks.privacy_compliance]
customer_data_request_url = "/webhooks/gdpr/data-request"
customer_deletion_url = "/webhooks/gdpr/customer-deletion"
shop_deletion_url = "/webhooks/gdpr/shop-deletion"Webhook Handler
import crypto from 'crypto';
function verifyWebhook(req) {
const hmac = req.headers['x-shopify-hmac-sha256'];
const body = req.rawBody; // Raw body buffer
const hash = crypto
.createHmac('sha256', process.env.SHOPIFY_API_SECRET)
.update(body, 'utf8')
.digest('base64');
return hmac === hash;
}
app.post('/webhooks/orders/create', async (req, res) => {
if (!verifyWebhook(req)) {
return res.status(401).send('Unauthorized');
}
const order = req.body;
console.log('New order:', order.id, order.name);
// Process order...
res.status(200).send('OK');
});Common Webhook Topics
Orders:
orders/create,orders/updated,orders/deleteorders/paid,orders/cancelled,orders/fulfilled
Products:
products/create,products/update,products/delete
Customers:
customers/create,customers/update,customers/delete
Inventory:
inventory_levels/update
App:
app/uninstalled(critical for cleanup)
Billing Integration
App Charges
One-time Charge:
mutation CreateCharge($input: AppPurchaseOneTimeInput!) {
appPurchaseOneTimeCreate(input: $input) {
appPurchaseOneTime {
id
name
price { amount }
status
confirmationUrl
}
userErrors { field message }
}
}Variables:
{
"input": {
"name": "Premium Feature",
"price": { "amount": 49.99, "currencyCode": "USD" },
"returnUrl": "https://your-app.com/billing/callback"
}
}Recurring Charge (Subscription):
mutation CreateSubscription($input: AppSubscriptionCreateInput!) {
appSubscriptionCreate(input: $input) {
appSubscription {
id
name
status
confirmationUrl
}
userErrors { field message }
}
}Variables:
{
"input": {
"name": "Monthly Subscription",
"returnUrl": "https://your-app.com/billing/callback",
"lineItems": [
{
"plan": {
"appRecurringPricingDetails": {
"price": { "amount": 29.99, "currencyCode": "USD" },
"interval": "EVERY_30_DAYS"
}
}
}
]
}
}Usage-based Billing:
mutation CreateUsageCharge($input: AppUsageRecordCreateInput!) {
appUsageRecordCreate(input: $input) {
appUsageRecord {
id
price { amount }
description
}
userErrors { field message }
}
}Metafields
Create Metafield
mutation CreateMetafield($input: MetafieldInput!) {
metafieldsSet(metafields: [$input]) {
metafields {
id
namespace
key
value
}
userErrors { field message }
}
}Variables:
{
"input": {
"ownerId": "gid://shopify/Product/123",
"namespace": "custom",
"key": "instructions",
"value": "Handle with care",
"type": "single_line_text_field"
}
}Metafield Types:
single_line_text_field,multi_line_text_fieldnumber_integer,number_decimaldate,date_timeurl,jsonfile_reference,product_reference
Rate Limiting
GraphQL Cost-Based Limits
Limits:
- Available points: 2000
- Restore rate: 100 points/second
- Max query cost: 2000
Check Cost:
const response = await graphqlRequest(shop, token, query);
const cost = response.extensions?.cost;
console.log(`Cost: ${cost.actualQueryCost}/${cost.throttleStatus.maximumAvailable}`);Handle Throttling:
async function graphqlWithRetry(shop, token, query, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await graphqlRequest(shop, token, query);
} catch (error) {
if (error.message.includes('Throttled') && i < retries - 1) {
await sleep(Math.pow(2, i) * 1000); // Exponential backoff
continue;
}
throw error;
}
}
}Best Practices
Security:
- Store credentials in environment variables
- Verify webhook HMAC signatures
- Validate OAuth state parameter
- Use HTTPS for all endpoints
- Implement rate limiting on your endpoints
Performance:
- Cache access tokens securely
- Use bulk operations for large datasets
- Implement pagination for queries
- Monitor GraphQL query costs
Reliability:
- Implement exponential backoff for retries
- Handle webhook delivery failures
- Log errors for debugging
- Monitor app health metrics
Compliance:
- Implement GDPR webhooks (mandatory)
- Handle customer data deletion requests
- Provide data export functionality
- Follow data retention policies
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_extensionConfiguration (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 = trueBasic 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_actionimport { 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 functionOrder 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
Themes Reference
Guide for developing Shopify themes with Liquid templating.
Liquid Templating
Syntax Basics
Objects (Output):
{{ product.title }}
{{ product.price | money }}
{{ customer.email }}Tags (Logic):
{% if product.available %}
<button>Add to Cart</button>
{% else %}
<p>Sold Out</p>
{% endif %}
{% for product in collection.products %}
{{ product.title }}
{% endfor %}
{% case product.type %}
{% when 'Clothing' %}
<span>Apparel</span>
{% when 'Shoes' %}
<span>Footwear</span>
{% else %}
<span>Other</span>
{% endcase %}Filters (Transform):
{{ product.title | upcase }}
{{ product.price | money }}
{{ product.description | strip_html | truncate: 100 }}
{{ product.image | img_url: 'medium' }}
{{ 'now' | date: '%B %d, %Y' }}Common Objects
Product:
{{ product.id }}
{{ product.title }}
{{ product.handle }}
{{ product.description }}
{{ product.price }}
{{ product.compare_at_price }}
{{ product.available }}
{{ product.type }}
{{ product.vendor }}
{{ product.tags }}
{{ product.images }}
{{ product.variants }}
{{ product.featured_image }}
{{ product.url }}Collection:
{{ collection.title }}
{{ collection.handle }}
{{ collection.description }}
{{ collection.products }}
{{ collection.products_count }}
{{ collection.image }}
{{ collection.url }}Cart:
{{ cart.item_count }}
{{ cart.total_price }}
{{ cart.items }}
{{ cart.note }}
{{ cart.attributes }}Customer:
{{ customer.email }}
{{ customer.first_name }}
{{ customer.last_name }}
{{ customer.orders_count }}
{{ customer.total_spent }}
{{ customer.addresses }}
{{ customer.default_address }}Shop:
{{ shop.name }}
{{ shop.email }}
{{ shop.domain }}
{{ shop.currency }}
{{ shop.money_format }}
{{ shop.enabled_payment_types }}Common Filters
String:
upcase,downcase,capitalizestrip_html,strip_newlinestruncate: 100,truncatewords: 20replace: 'old', 'new'
Number:
money- Format currencyround,ceil,floortimes,divided_by,plus,minus
Array:
join: ', 'first,lastsizemap: 'property'where: 'property', 'value'
URL:
img_url: 'size'- Image URLurl_for_type,url_for_vendorlink_to,link_to_type
Date:
date: '%B %d, %Y'
Theme Architecture
Directory Structure
theme/
├── assets/ # CSS, JS, images
├── config/ # Theme settings
│ ├── settings_schema.json
│ └── settings_data.json
├── layout/ # Base templates
│ └── theme.liquid
├── locales/ # Translations
│ └── en.default.json
├── sections/ # Reusable blocks
│ ├── header.liquid
│ ├── footer.liquid
│ └── product-grid.liquid
├── snippets/ # Small components
│ ├── product-card.liquid
│ └── icon.liquid
└── templates/ # Page templates
├── index.json
├── product.json
├── collection.json
└── cart.liquidLayout
Base template wrapping all pages (layout/theme.liquid):
<!DOCTYPE html>
<html lang="{{ request.locale.iso_code }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{ page_title }}</title>
{{ content_for_header }}
<link rel="stylesheet" href="{{ 'theme.css' | asset_url }}">
</head>
<body>
{% section 'header' %}
<main>
{{ content_for_layout }}
</main>
{% section 'footer' %}
<script src="{{ 'theme.js' | asset_url }}"></script>
</body>
</html>Templates
Page-specific structures (templates/product.json):
{
"sections": {
"main": {
"type": "product-template",
"settings": {
"show_vendor": true,
"show_quantity_selector": true
}
},
"recommendations": {
"type": "product-recommendations"
}
},
"order": ["main", "recommendations"]
}Legacy format (templates/product.liquid):
<div class="product">
<div class="product-images">
<img src="{{ product.featured_image | img_url: 'large' }}" alt="{{ product.title }}">
</div>
<div class="product-details">
<h1>{{ product.title }}</h1>
<p class="price">{{ product.price | money }}</p>
{% form 'product', product %}
<select name="id">
{% for variant in product.variants %}
<option value="{{ variant.id }}">{{ variant.title }} - {{ variant.price | money }}</option>
{% endfor %}
</select>
<button type="submit">Add to Cart</button>
{% endform %}
</div>
</div>Sections
Reusable content blocks (sections/product-grid.liquid):
<div class="product-grid">
{% for product in section.settings.collection.products %}
<div class="product-card">
<a href="{{ product.url }}">
<img src="{{ product.featured_image | img_url: 'medium' }}" alt="{{ product.title }}">
<h3>{{ product.title }}</h3>
<p>{{ product.price | money }}</p>
</a>
</div>
{% endfor %}
</div>
{% schema %}
{
"name": "Product Grid",
"settings": [
{
"type": "collection",
"id": "collection",
"label": "Collection"
},
{
"type": "range",
"id": "products_per_row",
"min": 2,
"max": 5,
"step": 1,
"default": 4,
"label": "Products per row"
}
],
"presets": [
{
"name": "Product Grid"
}
]
}
{% endschema %}Snippets
Small reusable components (snippets/product-card.liquid):
<div class="product-card">
<a href="{{ product.url }}">
{% if product.featured_image %}
<img src="{{ product.featured_image | img_url: 'medium' }}" alt="{{ product.title }}">
{% endif %}
<h3>{{ product.title }}</h3>
<p class="price">{{ product.price | money }}</p>
{% if product.compare_at_price > product.price %}
<p class="sale-price">{{ product.compare_at_price | money }}</p>
{% endif %}
</a>
</div>Include snippet:
{% render 'product-card', product: product %}Development Workflow
Setup
# Initialize new theme
shopify theme init
# Choose Dawn (reference theme) or blankLocal Development
# Start local server
shopify theme dev
# Preview at http://localhost:9292
# Changes auto-sync to development themePull Theme
# Pull live theme
shopify theme pull --live
# Pull specific theme
shopify theme pull --theme=123456789
# Pull only templates
shopify theme pull --only=templatesPush Theme
# Push to development theme
shopify theme push --development
# Create new unpublished theme
shopify theme push --unpublished
# Push specific files
shopify theme push --only=sections,snippetsTheme Check
Lint theme code:
shopify theme check
shopify theme check --auto-correctCommon Patterns
Product Form with Variants
{% form 'product', product %}
{% unless product.has_only_default_variant %}
{% for option in product.options_with_values %}
<div class="product-option">
<label>{{ option.name }}</label>
<select name="options[{{ option.name }}]">
{% for value in option.values %}
<option value="{{ value }}">{{ value }}</option>
{% endfor %}
</select>
</div>
{% endfor %}
{% endunless %}
<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
<input type="number" name="quantity" value="1" min="1">
<button type="submit" {% unless product.available %}disabled{% endunless %}>
{% if product.available %}Add to Cart{% else %}Sold Out{% endif %}
</button>
{% endform %}Pagination
{% paginate collection.products by 12 %}
{% for product in collection.products %}
{% render 'product-card', product: product %}
{% endfor %}
{% if paginate.pages > 1 %}
<div class="pagination">
{% if paginate.previous %}
<a href="{{ paginate.previous.url }}">Previous</a>
{% endif %}
{% for part in paginate.parts %}
{% if part.is_link %}
<a href="{{ part.url }}">{{ part.title }}</a>
{% else %}
<span class="current">{{ part.title }}</span>
{% endif %}
{% endfor %}
{% if paginate.next %}
<a href="{{ paginate.next.url }}">Next</a>
{% endif %}
</div>
{% endif %}
{% endpaginate %}Cart AJAX
// Add to cart
fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: variantId,
quantity: 1
})
})
.then(res => res.json())
.then(item => console.log('Added:', item));
// Get cart
fetch('/cart.js')
.then(res => res.json())
.then(cart => console.log('Cart:', cart));
// Update cart
fetch('/cart/change.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: lineItemKey,
quantity: 2
})
})
.then(res => res.json());Metafields in Themes
Access custom data:
{{ product.metafields.custom.care_instructions }}
{{ product.metafields.custom.material.value }}
{% if product.metafields.custom.featured %}
<span class="badge">Featured</span>
{% endif %}Best Practices
Performance:
- Optimize images (use appropriate sizes)
- Minimize Liquid logic complexity
- Use lazy loading for images
- Defer non-critical JavaScript
Accessibility:
- Use semantic HTML
- Include alt text for images
- Support keyboard navigation
- Ensure sufficient color contrast
SEO:
- Use descriptive page titles
- Include meta descriptions
- Structure content with headings
- Implement schema markup
Code Quality:
- Follow Shopify theme guidelines
- Use consistent naming conventions
- Comment complex logic
- Keep sections focused and reusable
Resources
- Theme Development: https://shopify.dev/docs/themes
- Liquid Reference: https://shopify.dev/docs/api/liquid
- Dawn Theme: https://github.com/Shopify/dawn
- Theme Check: https://shopify.dev/docs/themes/tools/theme-check
Shopify GraphQL Admin API - Comprehensive Analysis
Source: https://shopify.dev/docs/api/admin-graphql Analysis Date: 2025-10-25 Thoroughness Level: Very Thorough
---
Executive Summary
The Shopify GraphQL Admin API is a powerful, modern API that enables developers to build apps and integrations that extend and enhance the Shopify admin experience. It provides efficient, flexible data fetching capabilities with type-safe operations for managing all aspects of a Shopify store.
Key Highlights
- GraphQL-based: Single endpoint with flexible query structure
- Comprehensive Coverage: Manages products, orders, customers, inventory, fulfillments, and more
- Versioned API: Stable releases with clear deprecation policies
- Rich Type System: Strongly-typed schema with introspection support
- Efficient Data Fetching: Request only the data you need, reducing over-fetching
- Batch Operations: Bulk queries and mutations for large-scale operations
- Real-time Capabilities: Webhook integration for event-driven workflows
---
1. API Overview
1.1 What is the Admin API?
The Shopify Admin API lets you build apps and integrations that extend and enhance the Shopify admin. It provides programmatic access to store data including:
- Products & Collections: Product catalog management
- Orders & Fulfillment: Order processing and shipping
- Customers: Customer data and segmentation
- Inventory: Stock management across locations
- Discounts & Pricing: Promotional campaigns
- Store Settings: Configuration and customization
- Analytics: Reporting and metrics
1.2 Why GraphQL?
GraphQL offers significant advantages over REST:
- Single Endpoint: All queries go to one endpoint
- Precise Data Fetching: Request exactly what you need
- Reduced Network Overhead: Fewer round trips
- Strong Typing: Self-documenting with introspection
- Nested Relationships: Fetch related data in one query
- Versioning: Backward-compatible evolution
---
2. Key Features
2.1 Core Capabilities
Flexible Querying
# Fetch specific fields only
query {
products(first: 10) {
edges {
node {
id
title
variants(first: 5) {
edges {
node {
price
inventoryQuantity
}
}
}
}
}
}
}Mutations for Data Modification
mutation {
productCreate(input: {
title: "New Product"
productType: "Apparel"
vendor: "Acme Corp"
}) {
product {
id
title
}
userErrors {
field
message
}
}
}Batch Operations
mutation {
bulkOperationRunQuery(
query: """
{
products {
edges {
node {
id
title
}
}
}
}
"""
) {
bulkOperation {
id
status
}
userErrors {
field
message
}
}
}2.2 Advanced Features
Pagination with Cursor-based Navigation
query {
products(first: 50, after: "eyJsYXN0X2lkIjo...") {
edges {
cursor
node {
id
title
}
}
pageInfo {
hasNextPage
hasPreviousPage
}
}
}Search and Filtering
query {
products(
first: 20,
query: "product_type:Apparel AND tag:summer"
) {
edges {
node {
id
title
tags
}
}
}
}Metafields for Custom Data
mutation {
productUpdate(input: {
id: "gid://shopify/Product/123"
metafields: [
{
namespace: "custom"
key: "fabric_type"
value: "cotton"
type: "single_line_text_field"
}
]
}) {
product {
id
metafields(first: 10) {
edges {
node {
namespace
key
value
}
}
}
}
}
}---
3. Common Operations
3.1 Query Operations
Product Management
- List Products:
productsquery with pagination - Get Product Details:
product(id:)with nested fields - Search Products: Using query parameter with search syntax
- Product Variants: Nested variant queries
Order Management
- List Orders:
ordersquery with date/status filters - Order Details:
order(id:)with line items, customer, shipping - Order Fulfillment:
fulfillmentOrdersfor fulfillment workflows - Order Transactions: Payment and transaction history
Customer Operations
- Customer List:
customerswith segmentation - Customer Profile:
customer(id:)with orders and addresses - Customer Search: Query-based customer lookup
Inventory Management
- Inventory Levels:
inventoryItemsacross locations - Stock Adjustments: Inventory quantity queries
- Location Management:
locationsquery
3.2 Mutation Operations
Product Mutations
# Create Product
productCreate(input: ProductInput!)
# Update Product
productUpdate(input: ProductInput!)
# Delete Product
productDelete(input: ProductDeleteInput!)
# Publish Product
productPublish(input: ProductPublishInput!)
# Create Variant
productVariantCreate(input: ProductVariantInput!)
# Bulk Product Updates
productVariantsBulkUpdate(productId: ID!, variants: [ProductVariantsBulkInput!]!)Order Mutations
# Create Draft Order
draftOrderCreate(input: DraftOrderInput!)
# Complete Draft Order
draftOrderComplete(id: ID!)
# Update Order
orderUpdate(input: OrderInput!)
# Cancel Order
orderCancel(orderId: ID!, reason: OrderCancelReason)
# Create Fulfillment
fulfillmentCreate(input: FulfillmentInput!)
# Add Order Note
orderUpdate(input: { id: ID!, note: String })Customer Mutations
# Create Customer
customerCreate(input: CustomerInput!)
# Update Customer
customerUpdate(input: CustomerInput!)
# Delete Customer
customerDelete(input: CustomerDeleteInput!)
# Add Customer Address
customerAddressCreate(customerId: ID!, address: MailingAddressInput!)Inventory Mutations
# Adjust Inventory
inventoryAdjustQuantity(input: InventoryAdjustQuantityInput!)
# Bulk Adjust Inventory
inventoryBulkAdjustQuantityAtLocation(inventoryItemAdjustments: [InventoryAdjustItemInput!]!, locationId: ID!)
# Move Inventory
inventoryMoveQuantity(input: InventoryMoveQuantityInput!)3.3 Bulk Operations
For large-scale data operations:
# Start Bulk Operation
mutation {
bulkOperationRunQuery(
query: """
{
products {
edges {
node {
id
title
status
}
}
}
}
"""
) {
bulkOperation {
id
status
}
}
}
# Check Bulk Operation Status
query {
node(id: "gid://shopify/BulkOperation/123") {
... on BulkOperation {
id
status
errorCode
objectCount
url
}
}
}---
4. API Structure
4.1 Type System
Object Types
- Product: Core product data and relationships
- Order: Order information and fulfillment
- Customer: Customer profiles and metadata
- Collection: Product groupings
- Fulfillment: Shipping and tracking
- InventoryItem: Stock keeping units
- Location: Physical/virtual store locations
- Shop: Store-level settings
Interface Types
- Node: Global ID interface
- HasMetafields: Metafield support interface
- HasPublishedTranslations: Translation support
Scalar Types
- ID: Global unique identifier (GID format)
- String: Text values
- Int: Integer numbers
- Float: Decimal numbers
- Boolean: True/false
- DateTime: ISO 8601 timestamps
- Money: Currency amounts
- URL: Valid URLs
- JSON: Raw JSON data
4.2 Connection Pattern
All list queries use Relay-style connections:
type ProductConnection {
edges: [ProductEdge!]!
pageInfo: PageInfo!
}
type ProductEdge {
cursor: String!
node: Product!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}4.3 Error Handling
UserErrors
type UserError {
field: [String!]
message: String!
}System Errors
- HTTP 429: Rate limit exceeded
- HTTP 500: Server errors
- HTTP 401: Authentication failed
- HTTP 403: Insufficient permissions
---
5. Best Practices
5.1 Query Optimization
Request Only Needed Fields
# Good - Specific fields
query {
products(first: 10) {
edges {
node {
id
title
status
}
}
}
}
# Avoid - Over-fetching
query {
products(first: 10) {
edges {
node {
id
title
description
vendor
productType
tags
variants(first: 100) {
edges {
node {
id
price
sku
inventoryQuantity
}
}
}
images(first: 100) {
edges {
node {
url
altText
}
}
}
}
}
}
}Use Pagination Wisely
- Fetch 50-100 items per page for optimal performance
- Use cursor-based pagination for consistency
- Store cursors for resumable operations
Batch Related Data
# Good - Single query with nested data
query {
order(id: "gid://shopify/Order/123") {
id
name
customer {
id
email
}
lineItems(first: 50) {
edges {
node {
title
quantity
variant {
id
price
}
}
}
}
}
}
# Avoid - Multiple separate queries
query {
order(id: "gid://shopify/Order/123") {
id
name
}
}
# Then separate query for customer
# Then separate query for line items5.2 Rate Limiting
Understand Rate Limits
- REST-based: 40 cost points per second (Basic), 80 (Advanced/Plus)
- GraphQL Cost Calculation: Based on query complexity
- Bucket System: Points refill over time
- Throttle Header:
X-Shopify-Shop-Api-Call-Limit
Cost Calculation
query {
products(first: 10) { # Cost: ~11 points (1 for query + 10 for items)
edges {
node {
id
title
variants(first: 5) { # Cost: ~5 points per product
edges {
node {
price
}
}
}
}
}
}
}
# Total approximate cost: ~61 pointsRate Limit Management
# Include query cost in response
query {
products(first: 10) {
edges {
node {
id
}
}
}
}
# Check extensions.cost for actual cost5.3 Authentication
App Authentication
# OAuth Access Token in Header
curl -X POST \
https://your-shop.myshopify.com/admin/api/2024-10/graphql.json \
-H 'Content-Type: application/json' \
-H 'X-Shopify-Access-Token: YOUR_ACCESS_TOKEN' \
-d '{"query": "{ shop { name } }"}'API Versioning
- Use stable API versions (e.g.,
2024-10) - Update to newer versions before deprecation
- Test with release candidate versions
5.4 Error Handling Best Practices
mutation {
productCreate(input: {
title: "New Product"
}) {
product {
id
title
}
userErrors {
field
message
}
}
}Always check: 1. userErrors: Business logic errors 2. HTTP status codes: System-level errors 3. extensions: Additional metadata
5.5 Idempotency
Use idempotency keys for mutations:
curl -X POST \
-H 'X-Shopify-Access-Token: TOKEN' \
-H 'X-Request-Id: unique-request-id-123' \
-d '{"query": "mutation { ... }"}'---
6. Typical Use Cases
6.1 E-commerce App Integration
Product Sync Application
- Query products from external system
- Create/update products in Shopify
- Sync inventory levels across platforms
- Handle variant mappings
Order Management System
- Fetch new orders via webhooks
- Update fulfillment status
- Generate shipping labels
- Send tracking information
6.2 Analytics and Reporting
Sales Dashboard
- Query orders with date filters
- Aggregate revenue data
- Customer segmentation analysis
- Product performance metrics
6.3 Inventory Management
Multi-location Inventory
- Track stock across warehouses
- Automate reordering
- Inventory transfers
- Stock level alerts
6.4 Customer Relationship Management
Customer Data Platform
- Import customer data
- Segment customers by behavior
- Track order history
- Manage customer tags and metadata
6.5 Marketing Automation
Discount Management
- Create promotional campaigns
- Apply dynamic pricing rules
- Customer-specific discounts
- Bulk discount operations
---
7. API Versions and Deprecation
7.1 Version Format
- Format:
YYYY-MM(e.g.,2024-10) - New versions quarterly
- Supported for 12 months minimum
- Deprecation announcements well in advance
7.2 Version Migration
# Specify version in endpoint
POST /admin/api/2024-10/graphql.json
# Check for deprecated fields
query {
product(id: "gid://shopify/Product/123") {
title
# Check API changelog for deprecated fields
}
}7.3 Staying Updated
- Monitor Shopify changelog
- Use latest stable version
- Test with release candidates
- Subscribe to deprecation notices
---
8. Tools and SDKs
8.1 Official SDKs
JavaScript/Node.js
const Shopify = require('@shopify/shopify-api');
const client = new Shopify.Clients.Graphql(
shop,
accessToken
);
const data = await client.query({
data: `{
products(first: 10) {
edges {
node {
id
title
}
}
}
}`,
});Ruby
require 'shopify_api'
ShopifyAPI::Context.setup(
api_key: "key",
api_secret_key: "secret",
scope: "read_products,write_orders",
host: "shop.myshopify.com"
)
client = ShopifyAPI::Clients::Graphql::Admin.new(
session: session
)
response = client.query(
query: "{ products(first: 10) { edges { node { id title } } } }"
)Python
import shopify
shopify.Session.setup(
api_key="key",
secret="secret"
)
session = shopify.Session(
"shop.myshopify.com",
"2024-10",
access_token
)
shopify.ShopifyResource.activate_session(session)
query = """
{
products(first: 10) {
edges {
node {
id
title
}
}
}
}
"""
result = shopify.GraphQL().execute(query)8.2 Development Tools
GraphiQL Explorer
- Interactive API explorer
- Schema introspection
- Query building interface
- Available in Shopify Partners dashboard
Shopify CLI
# Install Shopify CLI
npm install -g @shopify/cli
# Create app
shopify app create
# Generate GraphQL queries
shopify app generate graphql-query
# Test API calls
shopify app graphql-query8.3 Testing Tools
GraphQL Playground
- Test queries and mutations
- Save query collections
- Share with team members
Postman Collection
- Pre-built API collections
- Environment variables
- Automated testing
---
9. Security Considerations
9.1 Access Scopes
Define minimal required scopes:
read_products
write_products
read_orders
write_orders
read_customers
write_customers
read_inventory
write_inventory9.2 Access Token Management
- Store tokens securely (encrypted at rest)
- Use environment variables
- Rotate tokens periodically
- Implement token refresh flow
9.3 Data Privacy
- Comply with GDPR/CCPA
- Implement data deletion on request
- Audit data access logs
- Minimize data collection
9.4 Webhook Security
const crypto = require('crypto');
function verifyWebhook(body, hmacHeader, secret) {
const hash = crypto
.createHmac('sha256', secret)
.update(body, 'utf8')
.digest('base64');
return hash === hmacHeader;
}---
10. Performance Optimization
10.1 Query Efficiency
Use Aliases for Multiple Queries
query {
featured: products(first: 10, query: "tag:featured") {
edges {
node {
id
title
}
}
}
new: products(first: 10, query: "created_at:>2024-10-01") {
edges {
node {
id
title
}
}
}
}Fragment Reuse
fragment ProductFields on Product {
id
title
status
vendor
productType
}
query {
products(first: 10) {
edges {
node {
...ProductFields
}
}
}
}10.2 Caching Strategies
Response Caching
- Cache frequently accessed data
- Use ETags for conditional requests
- Implement cache invalidation
Webhook-driven Updates
- Subscribe to relevant webhooks
- Update cache on data changes
- Reduce polling frequency
10.3 Bulk Operations Best Practices
# For large datasets, use bulk operations
mutation {
bulkOperationRunQuery(
query: """
{
products {
edges {
node {
id
title
variants {
edges {
node {
id
inventoryQuantity
}
}
}
}
}
}
}
"""
) {
bulkOperation {
id
status
}
}
}
# Poll for completion
query {
currentBulkOperation {
id
status
errorCode
createdAt
completedAt
objectCount
fileSize
url
partialDataUrl
}
}---
11. Common Patterns
11.1 Product Catalog Sync
# 1. Fetch all products
query {
products(first: 250) {
edges {
cursor
node {
id
title
status
updatedAt
}
}
pageInfo {
hasNextPage
}
}
}
# 2. Update products based on external data
mutation UpdateProduct($input: ProductInput!) {
productUpdate(input: $input) {
product {
id
title
}
userErrors {
field
message
}
}
}
# 3. Create new products
mutation CreateProduct($input: ProductInput!) {
productCreate(input: $input) {
product {
id
title
}
userErrors {
field
message
}
}
}11.2 Order Fulfillment Workflow
# 1. Get unfulfilled orders
query {
orders(
first: 50,
query: "fulfillment_status:unfulfilled"
) {
edges {
node {
id
name
fulfillmentOrders(first: 10) {
edges {
node {
id
status
lineItems(first: 50) {
edges {
node {
id
remainingQuantity
}
}
}
}
}
}
}
}
}
}
# 2. Create fulfillment
mutation {
fulfillmentCreateV2(
fulfillment: {
lineItemsByFulfillmentOrder: [
{
fulfillmentOrderId: "gid://shopify/FulfillmentOrder/123"
fulfillmentOrderLineItems: [
{
id: "gid://shopify/FulfillmentOrderLineItem/456"
quantity: 2
}
]
}
]
trackingInfo: {
company: "UPS"
number: "1Z999AA10123456784"
url: "https://www.ups.com/track?tracknum=1Z999AA10123456784"
}
notifyCustomer: true
}
) {
fulfillment {
id
status
}
userErrors {
field
message
}
}
}11.3 Inventory Management
# 1. Check inventory levels
query {
location(id: "gid://shopify/Location/123") {
id
name
inventoryLevels(first: 250) {
edges {
node {
id
available
item {
id
sku
}
}
}
}
}
}
# 2. Adjust inventory
mutation {
inventoryAdjustQuantity(
input: {
inventoryLevelId: "gid://shopify/InventoryLevel/123?inventory_item_id=456"
availableDelta: 10
}
) {
inventoryLevel {
id
available
}
userErrors {
field
message
}
}
}
# 3. Set inventory quantity
mutation {
inventorySetQuantities(
input: {
reason: "correction"
quantities: [
{
inventoryItemId: "gid://shopify/InventoryItem/789"
locationId: "gid://shopify/Location/123"
quantity: 100
}
]
}
) {
inventoryAdjustmentGroup {
createdAt
reason
changes {
name
delta
}
}
userErrors {
field
message
}
}
}---
12. Troubleshooting
12.1 Common Errors
Authentication Errors
{
"errors": [
{
"message": "Access denied for field on Shop",
"extensions": {
"code": "ACCESS_DENIED",
"typeName": "Shop"
}
}
]
}Solution: Check access scopes and token validity
Rate Limit Errors
{
"errors": [
{
"message": "Throttled",
"extensions": {
"code": "THROTTLED"
}
}
]
}Solution: Implement exponential backoff, reduce query complexity
Validation Errors
{
"data": {
"productCreate": {
"product": null,
"userErrors": [
{
"field": ["title"],
"message": "Title can't be blank"
}
]
}
}
}Solution: Validate input before submission
12.2 Debugging Techniques
Enable Query Logging
const response = await client.query({
data: query,
extraHeaders: {
'X-GraphQL-Cost-Include-Fields': 'true'
}
});
console.log(response.extensions.cost);Use GraphiQL for Testing
- Test queries interactively
- View schema documentation
- Check field deprecation warnings
Monitor API Calls
// Log all requests
client.on('request', (req) => {
console.log('Request:', req);
});
client.on('response', (res) => {
console.log('Response:', res);
console.log('Rate Limit:', res.headers['x-shopify-shop-api-call-limit']);
});---
13. Resources and Further Learning
13.1 Official Documentation
- Shopify GraphQL Admin API Reference: https://shopify.dev/docs/api/admin-graphql
- GraphQL Learning: https://shopify.dev/docs/apps/build/graphql
- API Versioning: https://shopify.dev/docs/api/usage/versioning
- Rate Limits: https://shopify.dev/docs/api/usage/rate-limits
13.2 Developer Tools
- Shopify Partners Dashboard: https://partners.shopify.com
- GraphiQL App: https://shopify-graphiql-app.shopifycloud.com
- Shopify CLI: https://shopify.dev/docs/api/shopify-cli
13.3 Community Resources
- Shopify Community Forums: https://community.shopify.com
- Shopify Developer Slack: https://shopifydevs.slack.com
- Stack Overflow: Tag
shopify+graphql
13.4 Example Applications
- Shopify App Templates: https://github.com/Shopify/shopify-app-template-node
- Sample Apps: https://github.com/Shopify/example-apps
---
14. Conclusion
The Shopify GraphQL Admin API provides a comprehensive, efficient, and developer-friendly way to interact with Shopify stores. By following best practices and leveraging the API's powerful features, developers can build robust, scalable applications that enhance the Shopify ecosystem.
Key Takeaways:
1. Start Simple: Begin with basic queries and gradually add complexity 2. Optimize Early: Consider query costs and pagination from the start 3. Handle Errors Gracefully: Implement proper error handling and retry logic 4. Stay Updated: Follow API versioning and deprecation guidelines 5. Use Official SDKs: Leverage battle-tested libraries when possible 6. Monitor Performance: Track API usage and optimize bottlenecks 7. Security First: Protect access tokens and implement proper authentication 8. Test Thoroughly: Use GraphiQL and test environments before production
Next Steps:
1. Review the API reference documentation 2. Set up a development store 3. Generate API credentials 4. Build a proof-of-concept integration 5. Implement production-ready error handling 6. Deploy and monitor in production
---
Appendix A: Quick Reference
A.1 Essential Queries
# Shop Information
{ shop { name email } }
# Products
{ products(first: 10) { edges { node { id title } } } }
# Orders
{ orders(first: 10) { edges { node { id name } } } }
# Customers
{ customers(first: 10) { edges { node { id email } } } }
# Inventory
{ inventoryItems(first: 10) { edges { node { id sku } } } }A.2 Essential Mutations
# Create Product
productCreate(input: {title: "Product"})
# Update Product
productUpdate(input: {id: "gid://shopify/Product/123", title: "Updated"})
# Create Order
draftOrderCreate(input: {lineItems: [{variantId: "gid://shopify/ProductVariant/123", quantity: 1}]})
# Fulfill Order
fulfillmentCreateV2(fulfillment: {...})
# Adjust Inventory
inventoryAdjustQuantity(input: {inventoryLevelId: "...", availableDelta: 10})A.3 Common Query Parameters
first: Int: Limit results (max 250)after: String: Cursor for paginationquery: String: Search/filter stringreverse: Boolean: Reverse sort ordersortKey: String: Sort field
A.4 HTTP Headers
Content-Type: application/json
X-Shopify-Access-Token: YOUR_ACCESS_TOKEN
X-Shopify-Storefront-Access-Token: (for Storefront API)
X-Request-Id: unique-id-for-idempotency
X-Shopify-Api-Version: 2024-10---
Document Version: 1.0 Last Updated: 2025-10-25 Maintained By: Claude Code - API Research Team
Related skills
FAQ
Which Shopify API should I use?
The GraphQL Admin API is the recommended primary API; the REST Admin API is legacy and in maintenance mode.
When should I build an app versus an extension?
Build an app for integrating external services or cross-store functionality; build an extension to customize checkout, admin pages, or POS.