Agent Skills: Shopify Development (2026)

Shopify app and storefront development. Use when writing, reviewing, or scaffolding Shopify apps (Remix/React Router), Hydrogen storefronts, theme app extensions, checkout UI extensions, Shopify Functions, or any code that touches Shopify APIs (Admin GraphQL, Storefront, App Bridge, Polaris). Triggers on shopify.app.toml, @shopify/* imports, Polaris components, Shopify CLI commands, Hydrogen/Oxygen, metafields/metaobjects, webhooks, or any Shopify development task.

UncategorizedID: JoaquinCampo/codex-skills/shopify

Install this agent skill to your local

pnpm dlx add-skill https://github.com/JoaquinCampo/Skills/tree/HEAD/shopify

Skill Files

Browse the full folder contents for shopify.

Download Skill

Loading file tree…

shopify/SKILL.md

Skill Metadata

Name
shopify
Description
Shopify app and storefront development. Use when writing, reviewing, or scaffolding Shopify apps (Remix/React Router), Hydrogen storefronts, theme app extensions, checkout UI extensions, Shopify Functions, or any code that touches Shopify APIs (Admin GraphQL, Storefront, App Bridge, Polaris). Triggers on shopify.app.toml, @shopify/* imports, Polaris components, Shopify CLI commands, Hydrogen/Oxygen, metafields/metaobjects, webhooks, or any Shopify development task.

Shopify Development (2026)

Comprehensive guide for building Shopify apps, extensions, Hydrogen storefronts, and integrations using current best practices.

DevMCP Server

Shopify provides an official MCP server for AI-assisted development. If not already configured:

# Claude Code
claude mcp add --transport stdio shopify-dev-mcp -- npx -y @shopify/dev-mcp@latest

The DevMCP server provides tools to search Shopify docs, explore API schemas (Admin GraphQL, Storefront, Functions, Liquid, Polaris, POS UI Extensions), and get up-to-date answers. Use it for API field lookups and schema exploration.

Stack & Tooling

| Layer | Current (2026) | |---|---| | App framework | React Router (formerly Remix) via @shopify/shopify-app-react-router | | Scaffold | shopify app init → React Router template | | CLI | @shopify/cli — init, dev, deploy, generate extension | | UI (embedded apps) | Polaris Web Components (not Polaris React — deprecated) | | UI (extensions) | Polaris Web Components + Preact (not React) — 64KB bundle limit | | App Bridge | CDN v4 — https://cdn.shopify.com/shopifycloud/app-bridge.js | | APIs | Admin GraphQL (primary), Storefront GraphQL, Functions API | | Custom data | Metafields + Metaobjects via TOML or GraphQL | | Headless | Hydrogen + Oxygen (React Router v7, Vite, edge rendering) | | Functions | Wasm — Rust (recommended) or JavaScript (Javy) | | Config | shopify.app.toml — single source of truth |

Critical:

  • REST Admin API is legacy since October 1, 2024. As of April 1, 2025, all new public apps must use GraphQL Admin API exclusively.
  • Shopify Scripts: no editing/publishing after April 15, 2026; all scripts stop executing June 30, 2026. Migrate to Functions now.
  • Polaris React (@shopify/polaris) is deprecated (maintenance mode only). Use Polaris Web Components.
  • UI Extensions (checkout, admin, POS) must use Preact (not React) for API 2025-10+. 64KB bundle size limit enforced at deploy.

Scaffolding

shopify app init                        # Interactive — choose React Router template
shopify app dev                         # Local dev with tunnel + hot reload
shopify app generate extension          # Add extensions (checkout UI, functions, theme, admin, POS)
shopify app deploy                      # Deploy app version (config + all extensions)

Directory Structure (React Router template)

my-app/
├── shopify.app.toml          # App config, scopes, webhooks, metaobjects
├── app/
│   ├── entry.server.tsx      # Server entry
│   ├── root.tsx              # Root layout
│   ├── routes/
│   │   ├── app.tsx           # Authenticated layout (App Bridge + Polaris)
│   │   ├── app._index.tsx    # App home page
│   │   └── webhooks.tsx      # Webhook handler
│   └── shopify.server.ts     # shopifyApp() config — auth, session storage
├── extensions/               # All extensions live here
│   ├── checkout-ui/
│   ├── theme-extension/
│   └── my-function/
├── prisma/
│   └── schema.prisma         # Session table (required)
└── package.json

Authentication & Sessions

  • App Bridge v4 (CDN) automatically handles session tokens — no manual JWT fetch needed. The manual setup guide is for legacy App Bridge 2.0 only.
  • Embedded apps use session tokens (JWT, 1-minute lifetime). Third-party cookies are blocked by browsers.
  • @shopify/shopify-app-react-router (recommended) or @shopify/shopify-app-remix (v4, still works) handle OAuth + token exchange automatically.
  • Session storage: Prisma adapter (@shopify/shopify-app-session-storage-prisma) — the Session table is required.
  • Use authenticate.admin(request) in loaders/actions for authenticated Admin API access.
  • Use authenticate.public.checkout(request) for checkout extension requests.
  • Never expose Admin API tokens in client-side code.
  • Use offline access tokens for bulk operations and webhooks (online tokens expire in 24h).
// app/shopify.server.ts
import { shopifyApp } from "@shopify/shopify-app-react-router";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import prisma from "./db.server";

const shopify = shopifyApp({
  sessionStorage: new PrismaSessionStorage(prisma),
  // API key, secret, scopes, and app URL are read from
  // shopify.app.toml + environment variables automatically
});
export default shopify;
export const authenticate = shopify.authenticate;

// In a route loader:
export async function loader({ request }: LoaderFunctionArgs) {
  const { admin } = await authenticate.admin(request);
  const response = await admin.graphql(`{ shop { name } }`);
  const { data } = await response.json();
  return { shopName: data.shop.name };
}

Migration note: If on @shopify/shopify-app-remix, migrate to @shopify/shopify-app-react-router — see migration guide.

Polaris Web Components (NOT Polaris React)

Polaris React is deprecated. Use Polaris Web Components — framework-agnostic custom HTML elements.

Setup

<head>
  <meta name="shopify-api-key" content="%SHOPIFY_API_KEY%" />
  <script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>
  <script src="https://cdn.shopify.com/shopifycloud/polaris.js"></script>
</head>

Usage in React (App Home)

function ProductForm() {
  const [name, setName] = useState("");
  return (
    <s-page title="New Product">
      <s-layout>
        <s-layout-section>
          <s-card>
            <s-form-layout>
              <s-text-field
                label="Product name"
                value={name}
                onInput={(e) => setName(e.currentTarget.value)}
                required
              />
              <s-button variant="primary" type="submit">Save</s-button>
            </s-form-layout>
          </s-card>
        </s-layout-section>
      </s-layout>
    </s-page>
  );
}

Key Patterns

  • Elements are prefixed with s- (e.g., s-card, s-button, s-badge, s-data-table).
  • Use onInput for real-time updates, onChange for on-blur/enter validation.
  • All form values are strings — parse numbers/booleans yourself.
  • tone applies semantic color (critical, success, info); variant sets visual weight (primary, secondary).
  • Links with target="auto" auto-detect internal (_self) vs external (_blank).
  • Do not apply custom CSS to Polaris components — they have built-in design system styling.

Navigation (App Bridge)

// Handle Shopify navigation events in Remix/React Router
useEffect(() => {
  const handler = (event) => {
    const href = event.target.getAttribute("href");
    if (href) navigate(href);
  };
  document.addEventListener("shopify:navigate", handler);
  return () => document.removeEventListener("shopify:navigate", handler);
}, [navigate]);

Use <s-navigation-menu> for sidebar nav. Use shopify.navigate() for programmatic navigation. Never use <a> tags or redirect from react-router in embedded apps — use Link from react-router or the redirect helper from authenticate.admin.

Admin GraphQL API

Query Patterns

// Simple query
const response = await admin.graphql(`
  query {
    products(first: 10) {
      edges {
        node { id title handle }
      }
    }
  }
`);

// Mutation with variables
const response = await admin.graphql(`
  mutation createProduct($input: ProductInput!) {
    productCreate(input: $input) {
      product { id title }
      userErrors { field message }
    }
  }
`, { variables: { input: { title: "New Product" } } });

// Always check userErrors
const { data } = await response.json();
if (data.productCreate.userErrors.length > 0) {
  throw new Error(data.productCreate.userErrors[0].message);
}

Rate Limiting

  • Admin GraphQL uses calculated query cost (cost points, not request count).
  • Complex queries with nested connections cost more points.
  • Use cursor-based pagination for large datasets.
  • For bulk data: use bulk operations (bulkOperationRunQuery / bulkOperationRunMutation) — up to 5 concurrent queries and 5 concurrent mutations per shop in API 2026-01+ (previously 1 each).
  • Use offline access tokens for bulk operations (online tokens expire before completion).

API Version

Always pin to a stable version (e.g., 2026-01). Set in shopify.app.toml:

[webhooks]
api_version = "2026-01"

Storefront API

  • For all customer-facing data: products, collections, cart, checkout.
  • No rate limits on request count — scales for buyer traffic surges.
  • Requires Storefront access token (public, safe for client-side).
  • Use in Hydrogen storefronts, mobile apps, or headless frontends.

Metafields & Metaobjects

When to Use Which

| Use Case | Tool | |---|---| | Add custom field to existing resource (product, order, customer) | Metafield | | Create standalone reusable structured data | Metaobject | | App-internal data model | Metaobject with $app: prefix in TOML |

App-Owned Metafields (TOML)

# shopify.app.toml — auto-created on `shopify app dev`
[[metaobjects]]
type = "$app:qrcode"

  [metaobjects.access]
  admin = "merchant_read_write"
  storefront = "public_read"

  [[metaobjects.fields]]
  key = "title"
  type = "single_line_text_field"
  name = "Title"
  required = true

  [[metaobjects.fields]]
  key = "product"
  type = "product_reference"
  name = "Product"

Merchant-Owned Metafields (GraphQL)

mutation {
  metafieldDefinitionCreate(definition: {
    name: "Warranty Info"
    namespace: "custom"       # NOT $app — merchant-owned
    key: "warranty_info"
    type: "multi_line_text_field"
    ownerType: PRODUCT
    access: { storefront: PUBLIC_READ }
  }) {
    createdDefinition { name namespace key }
  }
}
  • $app namespace = app-owned (read-only in admin by default).
  • custom or other namespaces = merchant-owned.
  • Use TOML for app-owned; GraphQL for merchant-owned.

Webhooks

Configuration (TOML)

[webhooks]
api_version = "2026-01"

[[webhooks.subscriptions]]
topics = ["orders/create", "products/update"]
uri = "/webhooks"

[[webhooks.subscriptions]]
compliance_topics = ["customers/data_request", "customers/redact", "shop/redact"]
uri = "https://app.example.com/webhooks"

Mandatory Compliance (Required for App Store)

Every public app must handle these three webhooks:

  1. customers/data_request — respond with customer data you store
  2. customers/redact — delete customer personal data
  3. shop/redact — delete all shop data after uninstall

Validate HMAC on every webhook. Return 401 for invalid HMAC, 200 for valid.

GraphQL Subscriptions

mutation {
  webhookSubscriptionCreate(
    topic: ORDERS_CREATE
    webhookSubscription: {
      callbackUrl: "https://app.example.com/webhooks"
      format: JSON
    }
  ) {
    webhookSubscription { id }
    userErrors { field message }
  }
}

Shopify Functions (Wasm)

Extend Shopify backend logic (discounts, validation, delivery, payment customization) as sandboxed WebAssembly modules.

Language Choice

  • Rust: 3x faster than JS. Required for public apps with complex logic or high line-item counts. Stays within instruction count limits.
  • JavaScript (via Javy → Wasm): Viable for prototyping and simple logic (<5ms realistic cases). Lower barrier to entry.

Execution Order in Checkout

  1. Cart Transform → 2. Discount (cart lines) → 3. Fulfillment/Order Routing → 4. Delivery Customization → 5. Discount (delivery) → 6. Payment Customization → 7. Cart Validation

Scaffold & Build

shopify app generate extension    # Choose function type
shopify app function schema       # Regenerate GraphQL schema
shopify app function run          # Test locally

Structure

extensions/my-discount/
├── shopify.extension.toml     # Extension config (target, API version)
├── src/
│   ├── run.graphql            # Input query — what data the function receives
│   └── run.rs (or run.js)     # Function logic
├── schema.graphql             # Generated API schema
└── Cargo.toml (or package.json)

Performance: Functions run during checkout — delays block purchases. Minimize instruction count. Avoid unnecessary data in run.graphql.

Extensions

Types

| Extension | Purpose | |---|---| | theme_app_extension | Liquid blocks/snippets in Online Store themes | | checkout_ui_extension | Custom UI at checkout extension points | | admin_action | Dropdown actions on admin resource pages | | admin_block | Embedded blocks on admin resource pages | | pos_ui_extension | POS interface extensions | | Functions | Backend logic (discounts, delivery, validation, payments) |

Theme App Extensions

Required for all new App Store apps (replaces manual theme code editing).

extensions/theme-extension/
├── shopify.extension.toml
├── blocks/
│   └── rating.liquid
├── snippets/
├── assets/
└── locales/

Checkout UI Extensions

20+ extension points (e.g., Checkout::Dynamic::Render). Use Polaris Web Components. Keep lightweight — mobile-first.

Critical: React is deprecated for UI extensions as of API 2025-10. Must use Preact for any API version 2025-10+. Vanilla React + ui-react package exceeds the 64KB bundle limit.

# shopify.extension.toml
api_version = "2026-01"

[[extensions]]
name = "my-checkout-ext"
handle = "my-checkout-ext"
type = "ui_extension"
// package.json dependencies for UI extensions
{
  "dependencies": {
    "preact": "^10.10.x",
    "@preact/signals": "^2.3.x",
    "@shopify/ui-extensions": "2026.1.x"
  }
}
// tsconfig.json for Preact JSX
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  }
}

Hydrogen & Oxygen (Headless)

Stack

  • Hydrogen: React framework (React Router v7 + Vite) with Storefront API utilities, streaming SSR, granular caching.
  • Oxygen: Shopify's edge runtime (based on Cloudflare workerd). Multiple preview environments. CI/CD via GitHub.
  • Vite-first: Legacy Remix compiler no longer supported — must use Vite.

Key Patterns

// Caching — be explicit
const { storefront } = await context.storefront.query(PRODUCT_QUERY, {
  cache: CacheLong(),    // or CacheShort(), CacheNone(), CacheCustom()
});

// Streaming for performance
export default function Product() {
  return (
    <Suspense fallback={<ProductSkeleton />}>
      <Await resolve={recommendedProducts}>
        {(products) => <ProductGrid products={products} />}
      </Await>
    </Suspense>
  );
}

AI Integration (2026)

Hydrogen on Oxygen supports Storefront MCP — build AI agents into storefronts for personalized recommendations and checkout guidance using real-time Storefront API data.

App Store Submission Checklist

  1. GraphQL only — no REST API calls
  2. Session tokens — no cookie-based auth
  3. Mandatory compliance webhooks — all three implemented and HMAC-validated
  4. Theme app extensions — no manual theme code injection
  5. Polaris Web Components — consistent admin UI
  6. Performance — must not reduce Lighthouse score by >10 points
  7. Privacy — GDPR/CCPA compliant, data handling documented
  8. Credentials — provide valid test credentials with full feature access
  9. Screencast — complete walkthrough of setup + functionality
  10. No Shopify trademarks in app icon/banner/screenshots
  11. Review timeline: 7–14 days. Statuses: Draft → Submitted → Reviewed → Published.

Common Pitfalls

  • Using <a> or bare redirect() in embedded apps (breaks iframe). Use Link from react-router or the redirect helper from authenticate.admin.
  • Using Polaris React (@shopify/polaris) instead of Polaris Web Components — deprecated, maintenance-only.
  • Using React in UI extensions targeting API 2025-10+ — must use Preact (64KB bundle limit).
  • Exposing Admin API tokens client-side.
  • Using REST API in new apps (legacy since Oct 2024; blocked for new public apps since April 2025).
  • Not validating webhook HMAC.
  • Using online access tokens for bulk operations (they expire in 24h).
  • Heavy checkout extensions that slow mobile performance.
  • Not handling userErrors in GraphQL mutations.
  • Missing the $app: namespace prefix for app-owned metaobjects.
  • Forgetting to run shopify app function schema after changing API version.
  • Using @shopify/shopify-app-remix for new projects instead of @shopify/shopify-app-react-router.