Shopify themes, apps & checkout skill

Use when building or customizing a Shopify store across its three code surfaces — themes (Liquid, Online Store 2.0 sections, blocks and JSON templates), apps (Remix with the versioned GraphQL Admin API and its query-cost model), and checkout (UI extensions, Functions, Web Pixels), plus the CLI flow and checkout-extensibility migration.

by ericrisco·MIT license·GitHub ↗

★ 108 Stars on the repo·Checked

npx degit ericrisco/rsc-harness/skills/shopify#main ~/.claude/skills/shopify-3

SKILL.md · 12.4 KB · installs the whole folder to ~/.claude/skills/shopify-3

Files of Shopify themes, apps & checkout

Files 1 file
Show the full text230 lines

Shopify themes, apps & checkout

The single authoritative skill for building and customizing a Shopify store. The mental model: a Shopify store is a hosted platform you extend at well-defined seams — never a server you control. You render on the storefront with Liquid, you mutate data through the versioned GraphQL Admin API, and you customize checkout through sandboxed extensions. The platform owns hosting, the database, PCI scope, and the checkout DOM; you own only the seams. Three surfaces, three toolchains — name the surface before you write a line of code.

Pinned stack (verify against shopify.dev before pinning in a repo):

  • Shopify CLI 4.x — auto-upgrades via the package manager it was installed with; skips CI, project-local installs, and major bumps. shopify app config push is removed; use shopify app deploy.
  • GraphQL Admin API 2026-04 — latest stable; supported window 2026-04 / 2026-01 / 2025-10 / 2025-07†. Each version is supported ~12 months. Pin apiVersion and bump quarterly. † 2025-07 is at the edge of its window — accessible only until 2026-07-16; treat it as sunsetting and do not pin it in new work. Re-check the live list at shopify.dev/docs/api/usage/versioning.
  • Remix app template (@shopify/shopify-app-remix, App Bridge, Polaris React). GraphQL > REST — REST Admin API is legacy; Shopify steers all new app work to GraphQL.
  • Dawn — Shopify's source-available reference theme; OS 2.0 architecture is the baseline.

Pick your surface first

Most Shopify mistakes are surface confusion — answering with a headless React build when the ask was a Liquid section, or editing checkout.liquid when the seam is now an extension. Branch here:

Surface You're working on… Tool & entry Reference
Theme .liquid files, {% schema %}, JSON templates, storefront rendering, merchant-editable content shopify theme dev on a Dawn-based theme references/liquid-themes.md
App embedded admin UI, reading/writing store data, webhooks, automation shopify app dev on the Remix template + Admin GraphQL references/apps-graphql.md
Checkout checkout/thank-you/order-status UI or logic, discounts, shipping, tracking Checkout UI extensions / Functions / Web Pixels references/checkout-extensibility.md

If the answer is "the React rendering layer of a headless storefront", that is ../nextjs/SKILL.md, not this skill — Shopify is only the data seam (Storefront API) there.

Theme surface — Online Store 2.0 architecture

OS 2.0 (GA 2021, sometimes marketed "3.0") is the architecture: JSON templates + sections- everywhere + theme blocks + @app blocks + dynamic sources. The file map:

layout/theme.liquid            # the HTML shell (one per theme)
templates/product.json         # JSON template: which sections render, in what order
sections/main-product.liquid   # a section: markup + {% schema %} of merchant settings
sections/*.liquid              # section groups (header/footer) live here too
blocks/*.liquid                # theme blocks (reusable, nestable) — OS 2.0
snippets/*.liquid              # partials rendered via {% render %}
config/settings_schema.json    # global theme settings

Rule: every section carries a {% schema %} with presets so merchants edit content in the theme editor without a deploy. The why: content belongs in section.settings and metafields, not in code — if a merchant has to ask you to change a headline, the section is built wrong.

<!-- Bad: copy hardcoded in Liquid; merchant can't touch it -->
<h2>Summer Sale — 20% off everything</h2>

<!-- Good: editable in the theme editor, with a preset so it appears in "Add section" -->
<h2>{{ section.settings.heading | escape }}</h2>
{% schema %}
{
  "name": "Promo banner",
  "settings": [
    { "type": "text", "id": "heading", "label": "Heading", "default": "Summer Sale" }
  ],
  "blocks": [{ "type": "@app" }],
  "presets": [{ "name": "Promo banner" }]
}
{% endschema %}

The { "type": "@app" } block lets merchant-installed apps drop content into your section. The Shopify Theme Store requires the main product and featured-product sections to support @app blocks. See references/liquid-themes.md for setting types, section groups, and dynamic sources.

Liquid rules

  • {% render %}, never {% include %}. render is scoped (the snippet only sees what you pass) and cacheable; include leaks the parent scope and is deprecated.

    {% comment %} Bad {% endcomment %}
    {% include 'price' %}
    {% comment %} Good — explicit, scoped, cacheable {% endcomment %}
    {% render 'price', product: product, variant: variant %}
    
  • Bound every collection loop with limit: and never nest unbounded loops — storefront render cost is real and slow pages cost conversions. {% for p in collection.products limit: 8 %}.

  • Pipe all dynamic output. | money for prices (raw values render cents/locale wrong), | escape for any user/merchant string (XSS), | json when emitting data into a <script>.

  • Push branchy logic into metafields/metaobjects, not conditional chains. A case/if ladder over product types is data pretending to be code — bind a metafield and let Liquid do a lookup.

CLI workflow

Theme work (hot-reloads against a dev theme; never edits live unprompted):

shopify theme dev                      # local preview + hot reload
shopify theme pull                     # sync live/named theme down
shopify theme push --only templates/*  # push a subset; --ignore excludes paths
shopify theme check                    # Theme Check linter — wire into CI

Multi-environment lives in shopify.theme.toml; --environment is repeatable (shopify theme push --environment staging --environment prod).

App work:

shopify app dev      # tunnel + env + reload; provisions admin.graphql()
shopify app deploy   # release app + extensions  (NOT `app config push` — removed in 4.x)

CLI 4.x auto-upgrades via your package manager but skips CI and project-local installs — pin the version in CI so a silent bump never changes a release.

App surface — Remix + GraphQL Admin API

Apps are the Remix template. The shape:

app/shopify.server.js     # shopifyApp({ apiVersion, sessionStorage, webhooks, ... })
app/routes/app.*.jsx      # embedded admin pages (App Bridge + Polaris)
shopify.app.toml          # app config, scopes, webhook subscriptions
extensions/*              # app extensions (theme app ext, UI ext, Functions, Flow)
  • Pin apiVersion in shopify.server and bump it quarterly — an unpinned client silently follows Shopify's default and can break on a version rollover.

  • Use the GraphQL Admin API, not REST. REST is legacy; new fields ship to GraphQL only.

    // Bad — legacy REST Admin endpoint
    await fetch(`https://${shop}/admin/api/2026-04/orders.json`);
    
    // Good — authenticated GraphQL through the template
    const { admin } = await authenticate.admin(request);
    const res = await admin.graphql(
      `#graphql
       query Orders { orders(first: 10) { nodes { id name } } }`
    );
    
  • Verify webhook HMAC before trusting any payload — authenticate.webhook(request) in the template does this; never parse a raw webhook body without it.

See references/apps-graphql.md for OAuth/session storage, mutations, App Bridge, and extensions.

GraphQL query-cost model

Admin GraphQL meters by calculated query cost (points), not request count. Every response carries extensions.cost:

{ "extensions": { "cost": {
  "requestedQueryCost": 92, "actualQueryCost": 30,
  "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1970, "restoreRate": 100 }
}}}
  • Over-budget returns HTTP 200 with a MAX_COST_EXCEEDED error — you must handle it in code; it is not an HTTP-level failure your client will throw on.
  • Read throttleStatus and back off on restoreRate (points restored per second) rather than blindly retrying.
  • Large reads use bulk operations, not paginated loops. A 10k-product export through first: pagination will throttle; bulkOperationRunQuery runs async and returns a JSONL file.

Checkout surface — the post-checkout.liquid model

checkout.liquid and additional scripts are deprecated and being removed. Dated facts:

  • 2024-08-13 — Information/Shipping/Payment steps lost checkout.liquid support.
  • 2025-08-28 — Plus self-migrate deadline for Thank-you & Order-status customizations (additional scripts, script tags, checkout.liquid). This was the deadline to act, not the auto-upgrade date.
  • 2026-01 — automatic upgrades of Thank-you & Order-status pages begin (30-day email notice); any remaining additional-scripts / script-tag / checkout.liquid customizations stop running.
  • 2026-04-15 — legacy Shopify Scripts can no longer be edited or published (existing scripts still run).
  • 2026-06-30 — legacy Shopify Scripts (Script Editor discount/shipping/payment scripts) stop executing entirely.

Migrate by surface — match the old mechanism to its new seam:

Old (deprecated) New seam Notes
checkout.liquid UI tweaks Checkout UI extensions sandboxed React/JS targets, not DOM access
Script Editor / additional-script discounts, shipping, payment logic Functions (Rust or JS → Wasm) deterministic, run server-side
<script> tracking / analytics in checkout Web Pixels + server-side events sandboxed; no arbitrary DOM scripts
custom checkout colors/fonts/CSS Checkout Branding API GraphQL, not CSS injection

Several checkout surfaces (full checkout UI customization, some Functions) are Shopify Plus-only. The full migration map, extension targets, and Functions structure are in references/checkout-extensibility.md.

App extensions catalog

  • Theme app extension — your app injects an @app block / blocks into themes (no theme edit).
  • Admin UI extension — surfaces inside admin pages (product, order) without leaving Shopify.
  • Customer-account UI extension — extends the new customer accounts.
  • Functions — discount / shipping / payment / cart logic as Wasm; the checkout.liquid logic seam.
  • Flow — triggers/actions for Shopify Flow automation, exposed by your app.

Anti-patterns

Anti-pattern Why it's wrong Do instead
{% include %} in new code deprecated, leaks parent scope, not cacheable {% render %} with explicit args
Raw {{ price }} / {{ user_input }} wrong locale/cents; XSS | money, | escape, | json
Editing checkout.liquid / additional scripts deprecated; auto-upgraded away starting 2026-01 UI extensions / Functions / Web Pixels
REST Admin calls in a new app legacy; new fields are GraphQL-only admin.graphql()
Unpinned or stale apiVersion breaks on Shopify's version rollover pin a supported version, bump quarterly
Paginated first: loop for big reads throttles on query cost bulkOperationRunQuery
Ignoring extensions.cost.throttleStatus silent MAX_COST_EXCEEDED at HTTP 200 read cost, back off on restoreRate
Hardcoded copy in Liquid merchant can't edit without a deploy section.settings / metafields
Section with no presets won't appear in "Add section" in the editor add a presets entry to {% schema %}
Secrets committed in shopify.app.toml leaks API credentials env vars; keep secrets out of TOML
No Theme Check in CI regressions ship to the storefront shopify theme check in the pipeline
Answering with a headless React build wrong surface for a Liquid/theme ask confirm the surface; route React to ../nextjs/SKILL.md

Run scripts/verify.sh <theme-or-app-dir> for an advisory scan of these foot-guns.

1---
2name: shopify
3description: "Use when building or customizing a Shopify store across its three code surfaces — themes (Liquid, Online Store 2.0 sections, blocks and JSON templates), apps (Remix with the versioned GraphQL Admin API and its query-cost model), and checkout (UI extensions, Functions, Web Pixels), plus the CLI flow and checkout-extensibility migration. NOT WooCommerce or PHP stores (that is `wordpress`), NOT the React layer of a headless storefront (that is `nextjs`), NOT non-Shopify payment integrations (that is `stripe`)."
4tags: [shopify, liquid, ecommerce, themes, checkout, graphql, storefront]
5recommends: [nextjs, stripe, wordpress, api-design, seo-geo]
6origin: risco
7---
8 
9# Shopify themes, apps & checkout
10 
11The single authoritative skill for building and customizing a Shopify store. The mental model:
12**a Shopify store is a hosted platform you extend at well-defined seams — never a server you
13control.** You render on the storefront with Liquid, you mutate data through the versioned
14GraphQL Admin API, and you customize checkout through sandboxed extensions. The platform owns
15hosting, the database, PCI scope, and the checkout DOM; you own only the seams. Three surfaces,
16three toolchains — name the surface before you write a line of code.
17 
18Pinned stack (verify against shopify.dev before pinning in a repo):
19 
20- **Shopify CLI 4.x** — auto-upgrades via the package manager it was installed with; skips CI,
21 project-local installs, and major bumps. `shopify app config push` is removed; use `shopify app deploy`.
22- **GraphQL Admin API `2026-04`** — latest stable; supported window 2026-04 / 2026-01 / 2025-10 /
23 2025-07†. Each version is supported ~12 months. Pin `apiVersion` and bump quarterly.
24 † `2025-07` is at the edge of its window — accessible only until 2026-07-16; treat it as
25 sunsetting and do not pin it in new work. Re-check the live list at shopify.dev/docs/api/usage/versioning.
26- **Remix app template** (`@shopify/shopify-app-remix`, App Bridge, Polaris React). GraphQL > REST —
27 REST Admin API is legacy; Shopify steers all new app work to GraphQL.
28- **Dawn** — Shopify's source-available reference theme; OS 2.0 architecture is the baseline.
29 
30## Pick your surface first
31 
32Most Shopify mistakes are surface confusion — answering with a headless React build when the ask
33was a Liquid section, or editing `checkout.liquid` when the seam is now an extension. Branch here:
34 
35| Surface | You're working on… | Tool & entry | Reference |
36|---|---|---|---|
37| **Theme** | `.liquid` files, `{% schema %}`, JSON templates, storefront rendering, merchant-editable content | `shopify theme dev` on a Dawn-based theme | `references/liquid-themes.md` |
38| **App** | embedded admin UI, reading/writing store data, webhooks, automation | `shopify app dev` on the Remix template + Admin GraphQL | `references/apps-graphql.md` |
39| **Checkout** | checkout/thank-you/order-status UI or logic, discounts, shipping, tracking | Checkout UI extensions / Functions / Web Pixels | `references/checkout-extensibility.md` |
40 
41If the answer is "the React rendering layer of a headless storefront", that is `../nextjs/SKILL.md`,
42not this skill — Shopify is only the data seam (Storefront API) there.
43 
44## Theme surface — Online Store 2.0 architecture
45 
46OS 2.0 (GA 2021, sometimes marketed "3.0") is the architecture: JSON templates + sections-
47everywhere + theme blocks + `@app` blocks + dynamic sources. The file map:
48 
49```text
50layout/theme.liquid # the HTML shell (one per theme)
51templates/product.json # JSON template: which sections render, in what order
52sections/main-product.liquid # a section: markup + {% schema %} of merchant settings
53sections/*.liquid # section groups (header/footer) live here too
54blocks/*.liquid # theme blocks (reusable, nestable) — OS 2.0
55snippets/*.liquid # partials rendered via {% render %}
56config/settings_schema.json # global theme settings
57```
58 
59**Rule: every section carries a `{% schema %}` with `presets` so merchants edit content in the
60theme editor without a deploy.** The why: content belongs in `section.settings` and metafields, not
61in code — if a merchant has to ask you to change a headline, the section is built wrong.
62 
63```liquid
64<!-- Bad: copy hardcoded in Liquid; merchant can't touch it -->
65<h2>Summer Sale — 20% off everything</h2>
66 
67<!-- Good: editable in the theme editor, with a preset so it appears in "Add section" -->
68<h2>{{ section.settings.heading | escape }}</h2>
69{% schema %}
70{
71 "name": "Promo banner",
72 "settings": [
73 { "type": "text", "id": "heading", "label": "Heading", "default": "Summer Sale" }
74 ],
75 "blocks": [{ "type": "@app" }],
76 "presets": [{ "name": "Promo banner" }]
77}
78{% endschema %}
79```
80 
81The `{ "type": "@app" }` block lets merchant-installed apps drop content into your section. The
82Shopify Theme Store **requires** the main product and featured-product sections to support `@app`
83blocks. See `references/liquid-themes.md` for setting types, section groups, and dynamic sources.
84 
85## Liquid rules
86 
87- **`{% render %}`, never `{% include %}`.** `render` is scoped (the snippet only sees what you
88 pass) and cacheable; `include` leaks the parent scope and is deprecated.
89 
90 ```liquid
91 {% comment %} Bad {% endcomment %}
92 {% include 'price' %}
93 {% comment %} Good — explicit, scoped, cacheable {% endcomment %}
94 {% render 'price', product: product, variant: variant %}
95 ```
96 
97- **Bound every collection loop with `limit:`** and never nest unbounded loops — storefront render
98 cost is real and slow pages cost conversions. `{% for p in collection.products limit: 8 %}`.
99- **Pipe all dynamic output.** `| money` for prices (raw values render cents/locale wrong),
100 `| escape` for any user/merchant string (XSS), `| json` when emitting data into a `<script>`.
101- **Push branchy logic into metafields/metaobjects, not conditional chains.** A `case`/`if` ladder
102 over product types is data pretending to be code — bind a metafield and let Liquid do a lookup.
103 
104## CLI workflow
105 
106Theme work (hot-reloads against a dev theme; never edits live unprompted):
107 
108```bash
109shopify theme dev # local preview + hot reload
110shopify theme pull # sync live/named theme down
111shopify theme push --only templates/* # push a subset; --ignore excludes paths
112shopify theme check # Theme Check linter — wire into CI
113```
114 
115Multi-environment lives in `shopify.theme.toml`; `--environment` is repeatable
116(`shopify theme push --environment staging --environment prod`).
117 
118App work:
119 
120```bash
121shopify app dev # tunnel + env + reload; provisions admin.graphql()
122shopify app deploy # release app + extensions (NOT `app config push` — removed in 4.x)
123```
124 
125CLI 4.x auto-upgrades via your package manager but skips CI and project-local installs — pin the
126version in CI so a silent bump never changes a release.
127 
128## App surface — Remix + GraphQL Admin API
129 
130Apps are the Remix template. The shape:
131 
132```text
133app/shopify.server.js # shopifyApp({ apiVersion, sessionStorage, webhooks, ... })
134app/routes/app.*.jsx # embedded admin pages (App Bridge + Polaris)
135shopify.app.toml # app config, scopes, webhook subscriptions
136extensions/* # app extensions (theme app ext, UI ext, Functions, Flow)
137```
138 
139- **Pin `apiVersion` in `shopify.server` and bump it quarterly** — an unpinned client silently
140 follows Shopify's default and can break on a version rollover.
141- **Use the GraphQL Admin API, not REST.** REST is legacy; new fields ship to GraphQL only.
142 
143 ```js
144 // Bad — legacy REST Admin endpoint
145 await fetch(`https://${shop}/admin/api/2026-04/orders.json`);
146 
147 // Good — authenticated GraphQL through the template
148 const { admin } = await authenticate.admin(request);
149 const res = await admin.graphql(
150 `#graphql
151 query Orders { orders(first: 10) { nodes { id name } } }`
152 );
153 ```
154 
155- **Verify webhook HMAC** before trusting any payload — `authenticate.webhook(request)` in the
156 template does this; never parse a raw webhook body without it.
157 
158See `references/apps-graphql.md` for OAuth/session storage, mutations, App Bridge, and extensions.
159 
160## GraphQL query-cost model
161 
162Admin GraphQL meters by **calculated query cost (points), not request count.** Every response
163carries `extensions.cost`:
164 
165```json
166{ "extensions": { "cost": {
167 "requestedQueryCost": 92, "actualQueryCost": 30,
168 "throttleStatus": { "maximumAvailable": 2000, "currentlyAvailable": 1970, "restoreRate": 100 }
169}}}
170```
171 
172- **Over-budget returns HTTP 200 with a `MAX_COST_EXCEEDED` error** — you must handle it in code; it
173 is not an HTTP-level failure your client will throw on.
174- **Read `throttleStatus` and back off on `restoreRate`** (points restored per second) rather than
175 blindly retrying.
176- **Large reads use bulk operations, not paginated loops.** A 10k-product export through `first:`
177 pagination will throttle; `bulkOperationRunQuery` runs async and returns a JSONL file.
178 
179## Checkout surface — the post-`checkout.liquid` model
180 
181`checkout.liquid` and additional scripts are **deprecated and being removed**. Dated facts:
182 
183- **2024-08-13** — Information/Shipping/Payment steps lost `checkout.liquid` support.
184- **2025-08-28** — Plus self-migrate deadline for Thank-you & Order-status customizations (additional
185 scripts, script tags, `checkout.liquid`). This was the *deadline to act*, not the auto-upgrade date.
186- **2026-01** — **automatic upgrades** of Thank-you & Order-status pages begin (30-day email notice);
187 any remaining additional-scripts / script-tag / `checkout.liquid` customizations stop running.
188- **2026-04-15** — legacy Shopify Scripts can no longer be edited or published (existing scripts still run).
189- **2026-06-30** — legacy Shopify Scripts (Script Editor discount/shipping/payment scripts) stop executing entirely.
190 
191Migrate by surface — match the old mechanism to its new seam:
192 
193| Old (deprecated) | New seam | Notes |
194|---|---|---|
195| `checkout.liquid` UI tweaks | **Checkout UI extensions** | sandboxed React/JS targets, not DOM access |
196| Script Editor / additional-script discounts, shipping, payment logic | **Functions** (Rust or JS → Wasm) | deterministic, run server-side |
197| `<script>` tracking / analytics in checkout | **Web Pixels** + server-side events | sandboxed; no arbitrary DOM scripts |
198| custom checkout colors/fonts/CSS | **Checkout Branding API** | GraphQL, not CSS injection |
199 
200Several checkout surfaces (full checkout UI customization, some Functions) are **Shopify Plus-only**.
201The full migration map, extension targets, and Functions structure are in
202`references/checkout-extensibility.md`.
203 
204## App extensions catalog
205 
206- **Theme app extension** — your app injects an `@app` block / blocks into themes (no theme edit).
207- **Admin UI extension** — surfaces inside admin pages (product, order) without leaving Shopify.
208- **Customer-account UI extension** — extends the new customer accounts.
209- **Functions** — discount / shipping / payment / cart logic as Wasm; the `checkout.liquid` logic seam.
210- **Flow** — triggers/actions for Shopify Flow automation, exposed by your app.
211 
212## Anti-patterns
213 
214| Anti-pattern | Why it's wrong | Do instead |
215|---|---|---|
216| `{% include %}` in new code | deprecated, leaks parent scope, not cacheable | `{% render %}` with explicit args |
217| Raw `{{ price }}` / `{{ user_input }}` | wrong locale/cents; XSS | `\| money`, `\| escape`, `\| json` |
218| Editing `checkout.liquid` / additional scripts | deprecated; auto-upgraded away starting 2026-01 | UI extensions / Functions / Web Pixels |
219| REST Admin calls in a new app | legacy; new fields are GraphQL-only | `admin.graphql()` |
220| Unpinned or stale `apiVersion` | breaks on Shopify's version rollover | pin a supported version, bump quarterly |
221| Paginated `first:` loop for big reads | throttles on query cost | `bulkOperationRunQuery` |
222| Ignoring `extensions.cost.throttleStatus` | silent `MAX_COST_EXCEEDED` at HTTP 200 | read cost, back off on `restoreRate` |
223| Hardcoded copy in Liquid | merchant can't edit without a deploy | `section.settings` / metafields |
224| Section with no `presets` | won't appear in "Add section" in the editor | add a `presets` entry to `{% schema %}` |
225| Secrets committed in `shopify.app.toml` | leaks API credentials | env vars; keep secrets out of TOML |
226| No Theme Check in CI | regressions ship to the storefront | `shopify theme check` in the pipeline |
227| Answering with a headless React build | wrong surface for a Liquid/theme ask | confirm the surface; route React to `../nextjs/SKILL.md` |
228 
229Run `scripts/verify.sh <theme-or-app-dir>` for an advisory scan of these foot-guns.
230 

Discussion

Alternatives

Also in Storefront & listingsSee all 136 in Sales →