Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3ed3ed2
Add unified Shopify umbrella skill
craigmichaelmartin Jul 21, 2026
bd5fbbd
Document shopify commands and --help for discovery
craigmichaelmartin Jul 21, 2026
269bec6
Make validation explicit about outcome type
craigmichaelmartin Jul 21, 2026
2475028
Fit skill description to Claude Code 250-char listing cap
craigmichaelmartin Jul 21, 2026
10e9463
Reference @shopify/cli for helper reuse, not cli-kit
craigmichaelmartin Jul 21, 2026
b243892
Add `shopify validate` command for offline validation
craigmichaelmartin Jul 21, 2026
80bb226
Clarify --api-name is an optional, unvalidated hint
craigmichaelmartin Jul 21, 2026
e23e9c9
Drop unverified --api-name customer from example
craigmichaelmartin Jul 21, 2026
00dc607
Merge pull request #8139 from Shopify/cli-worktree-1
craigmichaelmartin Jul 21, 2026
0315350
Enumerate valid --api-name values in the shopify skill
craigmichaelmartin Jul 21, 2026
fea2389
Revert --api-name doc-search guidance to how it was
craigmichaelmartin Jul 21, 2026
595ef16
Enumerate valid --api-name values in the shopify skill
craigmichaelmartin Jul 21, 2026
6dcf22f
limit validation retries to 3 attempts
craigmichaelmartin Jul 22, 2026
fe1b166
Invert shopify skill to validate-first, search-on-failure
craigmichaelmartin Jul 22, 2026
1fbb89a
Warn against unnecessary `shopify doc fetch`
craigmichaelmartin Jul 22, 2026
8ecc943
Enumerate the shopify validate topics and fix the mapping
craigmichaelmartin Jul 22, 2026
5391f13
Merge remote-tracking branch 'origin/feature/shopify-validate-command…
craigmichaelmartin Jul 22, 2026
5785bad
Merge pull request #8158 from Shopify/skill-validate-first-search-fal…
craigmichaelmartin Jul 22, 2026
cc8126c
Add per-topic agent files and make reading them step 1 of the skill
craigmichaelmartin Jul 23, 2026
e6662b1
Frame pre-validation doc search as optional, incorporate eval-run lea…
craigmichaelmartin Jul 23, 2026
8b3a8e5
Make topic agent files stateless
craigmichaelmartin Jul 23, 2026
a7ca1e6
Pluralize task in the shopify skill description
alfonso-noriega Jul 23, 2026
6aa0401
Revert skill description pluralization
alfonso-noriega Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
120 changes: 120 additions & 0 deletions .agents/skills/shopify/SKILL.md

Large diffs are not rendered by default.

143 changes: 143 additions & 0 deletions .agents/skills/shopify/topics/admin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Admin GraphQL API

Server-side API for reading/writing shop data: products, orders, customers, inventory, metafields, discounts, webhooks, bulk operations.

## Key facts

- Latest stable version: `2026-07`. Quarterly releases; supported: `2025-10`, `2026-01`, `2026-04`, `2026-07`. Requests to a retired version silently fall forward to the oldest supported one — always pin the version. Endpoint: `POST https://{shop}.myshopify.com/admin/api/2026-07/graphql.json` with headers `X-Shopify-Access-Token: {token}` and `Content-Type: application/json`.
- REST Admin API is legacy since Oct 1, 2024. Build everything on GraphQL; `/products.json`, `/variants.json` etc. are deprecated.
- Product model: `ProductInput.variants` and `.options` were removed (deprecated 2024-04). Define options via `productOptions` on `productCreate`; manage variants with `productVariantsBulkCreate|Update|Delete`, options with `productOptionsCreate`/`productOptionUpdate`/`productOptionsDelete`, or do a full declarative sync with `productSet`. Limit: 2048 variants per product.
- `productCreate` creates an UNPUBLISHED product with only its default variant; publish with `publishablePublish`.
- Inventory: the `@idempotent(key:)` directive (UUID key) is REQUIRED on `inventorySetQuantities` and `inventoryAdjustQuantities` since `2026-04`. `compareQuantity`/`ignoreCompareQuantity` were removed: each `quantities` item takes a mandatory `changeFromQuantity` (current expected qty for compare-and-swap, or explicit `null` to skip; mismatch fails with `CHANGE_FROM_QUANTITY_STALE`).
- Metafields: write with `metafieldsSet` — there is no metafieldCreate/Update. Max 25 metafields per call, 10MB payload, atomic. `type` is required unless a definition exists for namespace+key+owner type. Optional `compareDigest` per metafield gives CAS safety. Omitting `namespace` uses the app-reserved namespace. `value` is always a string, even for types like `number_integer`, `boolean`, `json`, `list.single_line_text_field`, `product_reference`.
- Webhooks: `WebhookSubscriptionInput.uri` (accepts `https://`, Pub/Sub, EventBridge ARN) replaced `callbackUrl`. Payload API version is configured app-wide, never per subscription. Prefer app-specific subscriptions in `shopify.app.toml` `[webhooks]` over this mutation.
- `currentBulkOperation` is deprecated → use `bulkOperation(id:)` or `bulkOperations(first:, query: "status:completed")` — no `status:` argument; filter via `query:`.
- Since `2026-01` an app can run up to 5 bulk operations of each type (query/mutation) per shop concurrently (previously 1 per type).
- `DiscountCodeBasicInput.customerSelection` is deprecated → use `context`.
- Every ID is a GID string, e.g. `"gid://shopify/Product/123"` — never bare integers.

## Core surface

| Query | Purpose |
|---|---|
| `shop` | Store settings, plan, currency |
| `products` / `product(id)` | List (supports `query:` filter) / fetch one |
| `orders` / `order(id)` | Orders; `read_orders` covers last 60 days only, older needs `read_all_orders` |
| `customers` / `customer(id)` | Customer records |
| `draftOrders`, `collections`, `collectionByIdentifier(identifier: {handle:})` | Drafts; collections by id/handle |
| `inventoryItems`, `locations` | Inventory items and stock locations |
| `metaobjects(type:)`, `metafieldDefinitions` | Custom data reads |
| `webhookSubscriptions`, `bulkOperations`, `files`, `companies` | Subscriptions, bulk job status, uploads, B2B |
| `publicApiVersions` | Supported API versions |

| Mutation | Purpose |
|---|---|
| `productCreate` / `productUpdate` / `productDelete` | Product CRUD (single default variant on create) |
| `productSet` | Declarative upsert of product+options+variants (sync use case) |
| `productVariantsBulkCreate` / `Update` / `Delete` | Variant management (up to 250/call) |
| `publishablePublish` | Publish product/collection to a sales channel |
| `tagsAdd` / `tagsRemove` | Tag any taggable resource |
| `metafieldsSet` / `metafieldsDelete` / `metafieldDefinitionCreate` | Metafield writes |
| `metaobjectDefinitionCreate` / `metaobjectUpsert` | Custom data models |
| `inventorySetQuantities` / `inventoryAdjustQuantities` | Absolute set / delta adjust (both need `@idempotent`) |
| `orderCreate` / `orderUpdate` | Import orders (max one discount code; automatic discounts not applied) / simple edits |
| `orderEditBegin` → edit → `orderEditCommit` | Line-item level order editing session |
| `draftOrderCreate` / `draftOrderComplete` | Draft order flow |
| `fulfillmentCreate` | Fulfill via `lineItemsByFulfillmentOrder` (FulfillmentOrder-based, not order-based) |
| `discountCodeBasicCreate` / `discountAutomaticBasicCreate` | Discounts |
| `stagedUploadsCreate` → HTTP upload → `fileCreate` | File/media upload flow |
| `webhookSubscriptionCreate` / `Update` / `Delete` | Webhook management |
| `bulkOperationRunQuery` / `bulkOperationRunMutation` / `bulkOperationCancel` | Async bulk export/import |
| `customerCreate` / `customerUpdate` | Customer CRUD |

## Examples (validated against the 2026-04 schema)

Filtered, paginated query — connections require `first` or `last` (max 250); page forward with `pageInfo.endCursor` → `after:`, backward with `startCursor` → `before:` + `last:`:

```graphql
query {
products(first: 50, query: "status:active created_at:>2026-01-01") {
nodes { id title variants(first: 10) { nodes { id sku price } } }
pageInfo { hasNextPage endCursor }
}
}
```

Create product with options (variants come after, via `productVariantsBulkCreate`):

```graphql
mutation {
productCreate(product: {
title: "Trail Runner Sock"
status: ACTIVE
productOptions: [{name: "Size", values: [{name: "S"}, {name: "M"}]}]
}) {
product { id options { name optionValues { name } } }
userErrors { field message }
}
}
```

Set metafields (upsert semantics):

```graphql
mutation {
metafieldsSet(metafields: [{
ownerId: "gid://shopify/Product/1234567890"
namespace: "custom"
key: "care_guide"
type: "single_line_text_field"
value: "Machine wash cold"
}]) {
metafields { id key value }
userErrors { field message code }
}
}
```

Set inventory with required idempotency key and CAS:

```graphql
mutation {
inventorySetQuantities(input: {
name: "available"
reason: "correction"
quantities: [{inventoryItemId: "gid://shopify/InventoryItem/30322695", locationId: "gid://shopify/Location/124656943", quantity: 42, changeFromQuantity: 40}]
}) @idempotent(key: "8f14e45f-ceea-4670-8a54-4c1b2f7e9d3a") {
inventoryAdjustmentGroup { changes { name delta } }
userErrors { field message code }
}
}
```

## Rate limits and cost

- Cost-based leaky bucket per app+store: 100 points/s (Standard), 200 (Advanced), 1000 (Plus), 2000 (Enterprise). Field costs: scalar/enum 0, object 1, mutation 10, connection sized by `first`/`last`. Single request cap: 1000 points (pre-execution, on requested cost; refunded down to actual cost).
- Throttled requests return a top-level `errors` entry with `extensions.code: THROTTLED` (HTTP still 200). Read `extensions.cost.throttleStatus { maximumAvailable currentlyAvailable restoreRate }` and back off.
- Input arrays max 250 items. Pagination capped at 25,000 objects; count fields return 25,001 to mean "more than 25,000". Use bulk operations beyond that.
- Resource throttles: stores over 50k variants get 1000 new variants/day (`productCreate`/`productUpdate`/`productVariantCreate`); dev/trial stores: max 5 `orderCreate`/min.

## Search syntax (`query:` argument)

`field:value` terms; bare space = `AND`, explicit `OR`; negate with `-` or `NOT`; comparators `:>` `:<` `:>=` `:<=`; ranges by combining; prefix wildcard `norm*`; existence `published_at:*` (negate for missing); quote phrases `title:"Trail Runner"`; escape `: \ ( )` with backslash. Filterable fields are listed per connection in the reference docs.

## Bulk operations

`bulkOperationRunQuery(query: "...")` wraps any single top-level connection query (nested connections allowed, no `first` needed). Poll `bulkOperation(id:)` / `bulkOperations` or subscribe to webhook topic `bulk_operations/finish`; when `status: COMPLETED`, download JSONL from `url` (expires after 7 days; `partialDataUrl` on failure). Imports: upload JSONL of variables via `stagedUploadsCreate` (resource `BULK_MUTATION_VARIABLES`), then `bulkOperationRunMutation(mutation:, stagedUploadPath:)`. `@idempotent` in a bulk mutation reads its key per JSONL row.

## Errors, auth, validation

- Mutations return errors in `userErrors { field message code }` with HTTP 200 — always select and check it; a missing check is the top integration bug.
- Access scopes are declared in `shopify.app.toml`: `[access_scopes] scopes = "read_products,write_products"`. The offline validator prints the exact scopes an operation needs: `shopify validate graphql --api admin --file op.graphql` (add `--version 2026-07` to target a specific version).
- Common validation failures: bare integer IDs instead of GIDs, missing `first:` on a connection, selecting scalars' subfields, using removed `ProductInput.variants`, omitting `@idempotent` on inventory mutations.

## Docs

https://shopify.dev/docs/api/admin-graphql/latest
https://shopify.dev/docs/api/usage/limits
https://shopify.dev/docs/api/usage/pagination-graphql
https://shopify.dev/docs/api/usage/search-syntax
https://shopify.dev/docs/api/usage/versioning
https://shopify.dev/docs/api/usage/bulk-operations/queries
https://shopify.dev/docs/api/usage/idempotent-requests
https://shopify.dev/docs/apps/build/graphql/migrate/learn-how
108 changes: 108 additions & 0 deletions .agents/skills/shopify/topics/app-store-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# App Store review
Pre-submission compliance for Shopify App Store apps: numbered requirements (sections 1–5), review workflow, AI self-review, and rules that most often fail.

## Key facts
- Requirements were renumbered (Dec 2025) into IDs `1.1.1`–`5.x.x` under five sections; cite by number.
- GraphQL Admin API is mandatory for new public apps since Apr 1 2025; REST Admin API is legacy since Oct 1 2024 (`2.2.4`). Only Theme/Asset API REST usage is exempt.
- App Bridge: the CDN `app-bridge.js` script tag, before any other script (ideally first in `<head>`), is required since Mar 13 2024 (`2.2.3`). The `@shopify/app-bridge` npm package is outdated and gets flagged; use `@shopify/app-bridge-react` or `@shopify/app-bridge-react-router`.
- Embedded apps must use session tokens, working without third-party cookies/localStorage even in Chrome incognito (`1.1.1`); auto-checked at submission since Jan 6 2025, plus AI listing/screenshot checks.
- Draft apps calling APIs deprecated in ≤90 days are blocked from submitting (since Jan 6 2025).
- Billing: **Shopify App Pricing** (managed: plans defined in the submission form, Shopify hosts the plan page, usage via App Events API) is the default; Billing API flows are now "Manual Pricing (Legacy)". Off-platform billing prohibited (`1.2.1`); plan changes must work in-app without reinstall (`1.2.3`).
- Reviews policy `1.3` (Jul 2026): incentivized/fake reviews risk review removal, delisting, or Partner termination; untrusted reviews are unpublished. Ask in neutral language, ideally via the Reviews API (below).
- `4.1.2` (Jul 2026): app name must be unique and lead with your distinctive brand identifier; copycat names get delisted.
- Current stable API version: `2026-07` (quarterly; each supported ≥12 months).
- Submissions and quality-check audits are managed in the Partner/Dev Dashboard (App > Distribution), not over email.

## Requirement map
| Section | Groups |
|---|---|
| 1 Policy | `1.1` operate in-platform (session tokens; Shopify checkout only; no marketplaces, agency brokering, lending, third-party POS; refunds only via `refundCreate`/`returnProcess`); `1.2` Shopify billing only; `1.3` honest review practices |
| 2 Functionality | `2.1` no UI/web errors (404/500/3xx block review); `2.2` Shopify APIs, latest App Bridge, GraphQL Admin API; `2.3` install/OAuth flow |
| 3 Security | `3.1` valid TLS/SSL; `3.2` justify sensitive scopes |
| 4 Listing | `4.1` unique/consistent name; `4.2` pricing only in Pricing details; `4.3` truthful (no stats, guarantees, testimonials); `4.4` clear unique images, no Shopify trademarks; `4.5` complete submission |
| 5 Category-specific | `5.1` online store; `5.2` payments; `5.3` payment facilitator; `5.4` purchase options; `5.5` product sourcing; `5.6` checkout; `5.7` sales channel; `5.8` post-purchase; `5.9` mobile app builders; `5.10` donation; `5.11` blockchain |

## Rules that most often fail
- OAuth immediately after install and reinstall, before any UI (`2.3.2`, `2.3.4`); redirect to app UI after the grant screen (`2.3.3`); never ask merchants to type a `.myshopify.com` domain (`2.3.1`).
- Sensitive scopes need demonstrated in-code use (`3.2.x`): `read_all_orders`, `write_payment_mandate`, `write_checkout_extensions_apis`, `read_advanced_dom_pixel_events`, `read_checkout_extensions_chat`.
- Theme changes only via theme app extensions (`5.1.1`); Asset API/ScriptTag injection needs an approved exemption; ship onboarding instructions plus theme-editor deep links (`5.1.3`).
- No self-promotion, cross-promotion, or review requests in admin extensions (`2.2.6`), checkout extensions (`5.6.2`/`5.6.3`), Sidekick extensions (`2.2.9`), or theme extensions. App Name Branding in storefront components only when customers interact with it as part of buying (payment method, loyalty); else standard attribution ≤24×24 px (`5.1.4`).
- Checkout: no countdown timers (`5.6.6`); never collect payment info in a UI extension (`5.6.9`); explicit buyer consent before any charge that raises order total (`1.1.9`, `5.6.5`); cheapest shipping stays default (`1.1.10`).
- Max modal (formerly fullscreen) only after merchant interaction, never from the nav (`2.2.7`).
- Post-purchase: max 2 consecutive offers (`5.8.4`); preset accept and decline buttons (`5.8.2`); redirect back to order confirmation (`5.8.6`).
- Payments apps: scopes limited to `write_payment_gateways` + `write_payment_sessions` (`5.2.4`), `embedded = false` (`5.2.5`), test mode required (`5.2.11`).

## Mandatory compliance webhooks
Every App Store app must subscribe—even if it stores no personal data. Handle JSON `POST`s, verify HMAC (`401` on invalid `X-Shopify-Hmac-SHA256`), acknowledge with `200`, act within 30 days.

| Topic | Trigger |
|---|---|
| `customers/data_request` | Customer wants their stored data; payload lists `orders_requested` IDs |
| `customers/redact` | Delete customer data; sent 10 days after request if no order in 6 months, else withheld 6 months |
| `shop/redact` | 48 h after uninstall; erase the shop's data |

```toml
[webhooks]
api_version = "2026-07"

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

Manual HMAC check (the React Router template's `authenticate.webhook` handles this):

```js
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.raw({type: '*/*'}));
app.post('/webhooks', (req, res) => {
const digest = crypto.createHmac('sha256', process.env.CLIENT_SECRET).update(req.body).digest('base64');
if (!crypto.timingSafeEqual(Buffer.from(digest, 'base64'), Buffer.from(req.headers['x-shopify-hmac-sha256'], 'base64'))) return res.status(401).send('invalid HMAC');
res.status(200).send('ok');
});
app.listen(3000);
```

1 s connect / 5 s total timeout; non-2xx (even 3xx) is failure; 8 failed retries over 4 h delete Admin API-configured subscriptions.

## Protected customer data
Level 1 = customer data excluding name, address, phone, email; level 2 includes those fields, each approved individually and subject to data protection reviews. Request via Partner Dashboard **API access requests**—impossible while under review; dev-store-only apps skip it. Unapproved fields come back redacted; GraphQL replies `200` with the redaction explained in `errors`. Level 1 requirements: data minimization, stated purposes, consent/opt-out handling, retention limits, encryption at rest and in transit. Level 2 adds encrypted backups, test/prod separation, data-loss prevention, limited staff access, access logs, incident response policy.

## Requesting reviews (Reviews API, App Home)
```js
const result = await shopify.reviews.request();
if (!result.success) console.log(`${result.code}: ${result.message}`);
```
Modal shows at most once per 60 days, 3× per 365 days; declined codes: `already-reviewed`, `mobile-app`, `merchant-ineligible`, `recently-installed` (<24 h), `cooldown-period`, `annual-limit-reached`, `already-open`, `open-in-progress`, `cancelled`. Dev stores bypass limits; their reviews aren't published. Request after a successful workflow, not on first open.

## Performance targets
- Storefront: must not drop Lighthouse score >10 points (weighted: home 17%, product 40%, collection 43%).
- Built for Shopify prerequisites: ≥50 net installs from active paid shops, ≥5 reviews, minimum recent rating. Admin Web Vitals at p75: LCP ≤2.5 s, CLS ≤0.1, INP ≤200 ms (≥100 calls/28 d each; measured via latest App Bridge). Checkout carrier rates: p95 ≤500 ms, ≤0.1% failures (≥1000 req/28 d).

## Review workflow
Statuses: **Draft → Submitted → Reviewed → Published**; **Paused** if core requirements block review (fix, then "Submit fixes"); **Suspended** after repeated bad submissions. Withdraw anytime.

Submission checklist: demo screencast of setup and core features (English or English subtitles—mandatory), valid full-access test credentials, emergency developer contact, app icon 1200×1200 JPEG/PNG, no "Shopify" (or misspellings) in app domains or API contact email, billing tested with `"test": true` then flipped to `"test": false`.

Listing rules: no pricing in images or the icon; each screenshot unique, showing actual app UI (no browser chrome or desktop backgrounds); accurate tags and language claims.

Failures forcing full re-submission: broken install/OAuth redirect, embedded/non-embedded switching, broken UI or web errors, missing test instructions, theme-extension violations. Billing fixes don't.

## AI self-review
Fetch the canonical code-checkable requirements (with per-requirement verification guidance):
```
shopify doc fetch --url https://shopify.dev/docs/apps/launch/app-store-review/app-store-ai-self-review-requirements
```
Grade each as likely passing / likely failing / needs review; numbering gaps are intentional (submission-time-only checks omitted). Category groups are gated by config signals: 5.1 needs `shopify.extension.toml` with `type = "theme"`; 5.2 `type = "payment"` + `write_payment_gateway` scope; 5.4 subscription-contract or payment-mandate scopes; 5.6 checkout-targeted `ui-extension`; 5.7 `type = "channel_config"`; 5.8 `type = "checkout_post_purchase"`; 5.3/5.5/5.9/5.10 are opt-in only. Listing content, live behavior, and UX still get human review after submission.

## Docs
https://shopify.dev/docs/apps/launch/shopify-app-store/app-store-requirements
https://shopify.dev/docs/apps/launch/app-store-review/app-store-ai-self-review-requirements
https://shopify.dev/docs/apps/launch/app-store-review/pass-app-review
https://shopify.dev/docs/apps/launch/app-store-review/submit-app-for-review
https://shopify.dev/docs/apps/build/compliance/privacy-law-compliance
https://shopify.dev/docs/apps/launch/protected-customer-data
https://shopify.dev/docs/apps/launch/built-for-shopify/requirements
https://shopify.dev/docs/apps/launch/marketing/manage-app-reviews
Loading
Loading