AgentStack
SKILL verified MIT Self-run

Shopify Category Taxonomy

skill-kgelster-awesome-ecom-skills-shopify-category-taxonomy · by kgelster

>-

No reviews yet
0 installs
10 views
0.0% view→install

Install

$ agentstack add skill-kgelster-awesome-ecom-skills-shopify-category-taxonomy

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Shopify Category Taxonomy? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Shopify Category Taxonomy

Two jobs that chain together: assign a product's Standard Product Taxonomy category (Part 1), then fill the category metafields that category exposes (Part 2). You cannot do Part 2 until Part 1 is true: the "Category metafields" panel only exists once a product is assigned to a category. Bulk CSV data work lives in the sibling shopify-matrixify; the one thing Matrixify cannot do is mint the reserved value metaobjects Part 2 needs, which is the entire reason this skill exists.

Store access

Lane A: custom-app token (scriptable). Shopify admin → Settings → Apps and sales channels → Develop apps → create an app → grant the four scopes below → install → copy the Admin API access token. Export it; never write it to disk:

export SHOPIFY_STORE="your-store.myshopify.com"
export SHOPIFY_ACCESS_TOKEN=""   # env, not disk
curl -s "https://$SHOPIFY_STORE/admin/api/2025-07/graphql.json" \
  -H "X-Shopify-Access-Token: $SHOPIFY_ACCESS_TOKEN" \
  -H "Content-Type: application/json" -d '{"query":"{ shop { name } }"}'

Minimum scopes: read_products, write_products, read_metaobjects, write_metaobjects. The metaobjects pair is not optional (see the null trap).

Lane B: Shopify CLI OAuth (no stored token). shopify store auth --store $SHOPIFY_STORE --scopes read_products,write_products,read_metaobjects,write_metaobjects then shopify store execute. Good for token-less stores where the owner logs in interactively. For the full Admin GraphQL schema, use Shopify's official AI toolkit plugin. That gives your agent the API; this skill gives it the playbook.

Recipes, not scripts

Treat every GraphQL block as a copy-and-adapt recipe your agent writes fresh, runs, and discards. Two rules keep that safe:

  1. Preview before you mutate. Precede every write with a read-only count of

the products in the target category and sanity-check it. A count far above expectation means stop and re-scope.

  1. Non-destructive defaults. Fill missing only; a blank value in

metafieldsSet deletes the metafield, so only emit non-blank values. Abort the run on first-batch userErrors.

Verify against ground truth via the Admin API, not the storefront (CDN-cached, lies about freshness). Never print or commit the token.

Part 1: Assign the category

Set each product's category field to a Standard Product Taxonomy category via productUpdate (or the Matrixify Category column for a pure bulk load).

CRITICAL: verify every category ID against the official taxonomy source; never guess an ID. A guessed taxonomy ID resolves to the wrong category or a dead node, and it fails silently: the product looks categorized but every downstream attribute, filter, and feed mapping is now wrong. Guessed IDs have burned real product feeds. Resolve the ID by search: against taxonomy { categories } (Part 2 step 1 shows the query), or from the official taxonomy repo linked in Provenance.

Classification signal priority (highest to lowest confidence): tags (highest on stores that tag by category: a "Mango" tea tagged black-tea is a flavored black, not a fruit infusion, and the tag says so where the title misleads) > product type (good when the store populates it, many don't) > title / vendor (lexical fallback; a mono-line vendor pins the category, a multi-line one doesn't) > description (last resort; verbose and easy to mis-key on flavor words).

Emit a confidence flag per product and split the catalog into high-confidence vs ambiguous (bundles, samplers, cross-category items). Ship the high-confidence rows; route the ambiguous ones to a human review sheet rather than guessing. See the SOP's pull-then-classify steps below.

# preview (read-only): how many products your classifier targets, before any write
query { productsCount(query: "tag:black-tea status:active") { count } }

# then, per high-confidence product, assign the category
mutation Assign($id: ID!, $cat: ID!) {
  productUpdate(product: {id: $id, category: $cat}) {
    product { id category { id fullName } }
    userErrors { field message }
  }
}

Part 2: Fill the category metafields

A category metafield is not a plain text/choice value. It is a metafield in the reserved shopify namespace, of type list.metaobject_reference, whose value is a JSON array of Metaobject GIDs. Each of those metaobjects is a reserved taxonomy value (type: "shopify--") with a label and a taxonomy_reference pointing at a global TaxonomyValue. The chain:

Product
  └─ metafield  shopify.  (list.metaobject_reference)
       └─ value: ["gid://shopify/Metaobject/…"]
            └─ Metaobject  type: shopify--   label: "Decaf"
                 └─ taxonomy_reference ─▶ TaxonomyValue/26137  (global)

These value metaobjects are minted on demand: a store only holds metaobjects for values it has actually used. A brand-new value must be created before you can reference it. That mint-then-reference step is what Matrixify cannot do.

Top 5 gotchas (fuller list in the reference):

  1. The null/scope trap. Without read_metaobjects, the value metaobjects

return null from node(), references, and metaobjectDefinitions: the whole feature looks read-only. With the scope they resolve. Minting needs write_metaobjects.

  1. A TaxonomyValue GID is rejected on write. metafieldsSet demands the

Metaobject GID, not the taxonomy value GID (else "Value require that you select a metaobject"). The TaxonomyValue GID is only the taxonomy_reference you pass when creating the metaobject.

  1. The product must already be in the category or the metafield doesn't

render as a category metafield. Scope targets by product.category, not by product type or title.

  1. GIDs are store-specific. Metaobject GIDs minted in one store do not port

to another. Re-mint per store.

  1. Matrixify cannot mint these. It can't create the reserved metaobjects and

won't resolve value names. The Admin API is the only reliable path.

SOP

1. Discover the category, its attributes, and allowed values.

query {
  taxonomy {
    categories(first: 1, search: "Tea & Infusions > Tea") {
      nodes {
        id fullName
        attributes(first: 50) {
          nodes {
            ... on TaxonomyChoiceListAttribute {
              id name
              values(first: 50) { nodes { id name } }   # id = TaxonomyValue GID
            }
            ... on TaxonomyMeasurementAttribute { id name }
          }
        }
      }
    }
  }
}

Record per attribute: the metafield key (the attribute handle, lowercased-hyphenated, confirm it by reading an already-set product in step 6; the Color attribute's key is color-pattern, not color) and the value name → TaxonomyValue GID map.

2. Pull the catalog, scoped to the target category. Do not classify blind: page every candidate product first, capturing the signals the classifier reads.

query($cursor: String) {
  products(first: 100, after: $cursor, query: "status:active") {
    pageInfo { hasNextPage endCursor }
    nodes {
      id handle title vendor productType tags
      category { id fullName }
      options { name optionValues { name } }
      metafields(namespace: "shopify", first: 25) { nodes { key jsonValue } }
    }
  }
}

For a large catalog, run this as a bulkOperationRunQuery dump instead of live pagination; the dump doubles as your pre-write backup.

3. Classify into high-confidence vs ambiguous. Apply the signal priority (tags first). Emit a confidence flag per product; the high-confidence rows ship, the ambiguous ones (bundles, samplers, cross-category, low-signal titles) route to a review spreadsheet a human corrects before you write anything. This split is what let one tea retailer ship 583 of 717 products high-confidence and hold the remaining 134 for review instead of guessing. Assign the category (Part 1) on the high-confidence rows now; then continue to mint/set below.

4. Mint each missing value metaobject (once per store). Reuse an existing metaobject when its label already matches; only mint when genuinely absent.

mutation M($m: MetaobjectCreateInput!) {
  metaobjectCreate(metaobject: $m) {
    metaobject { id displayName } userErrors { field message code }
  }
}
# variables:
# {"m":{"type":"shopify--caffeine-content","handle":"decaf",
#   "fields":[{"key":"label","value":"Decaf"},
#             {"key":"taxonomy_reference","value":"gid://shopify/TaxonomyValue/26137"}]}}

Build a value name → Metaobject GID map from the results plus existing entries.

5. Apply via metafieldsSet, batches of ≤25 metafields per call.

mutation S($m: [MetafieldsSetInput!]!) {
  metafieldsSet(metafields: $m) {
    metafields { id } userErrors { field message code }
  }
}
# each entry: {ownerId, namespace:"shopify", key:"tea-variety",
#   type:"list.metaobject_reference",
#   value:"[\"gid://shopify/Metaobject/…\"]"}

Emit only non-blank values. Abort on first-batch errors.

6. Verify by reading back resolved labels, not by trusting the write's userErrors: [].

query { product(id: "gid://shopify/Product/…") {
  metafields(namespace: "shopify", first: 10) { nodes {
    key references(first: 5) { nodes { ... on Metaobject { displayName } } } } } } }

Spot-check several products; confirm every name resolves to what you intended.

References

  • [references/gotchas-and-scoping.md](references/gotchas-and-scoping.md):

gotchas 6-10 (freeform labels, per-type mint fields, multi-category scoping), which surface each attribute reaches (Google feed vs Search & Discovery vs JSON-LD), and two worked examples with real numbers.

Provenance and maintenance

Last verified: 2026-07-05. GraphQL pinned to Admin API 2025-07; Shopify deprecates versions on a rolling quarterly schedule, so verify field shapes against shopify.dev and the metafields/metaobjects docs before trusting a version-specific claim. The category IDs and taxonomy values come from Shopify's Standard Product Taxonomy: the canonical source to verify an ID against; never guess one.

Read-only re-verification a stranger can run (no writes):

# 1. token reaches the store and reads a product's assigned category
curl -s "https://$SHOPIFY_STORE/admin/api/2025-07/graphql.json" \
  -H "X-Shopify-Access-Token: $SHOPIFY_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query":"{ products(first:1){ nodes{ category { id name } } } }"}'

# 2. taxonomy discovery resolves (confirms the taxonomy API + your read scope)
curl -s "https://$SHOPIFY_STORE/admin/api/2025-07/graphql.json" \
  -H "X-Shopify-Access-Token: $SHOPIFY_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query":"{ taxonomy { categories(first:1, search:\"Tea\"){ nodes{ id fullName } } } }"}'

These skills capture operational lessons the docs don't; they are not a replacement for the reference.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.