Use when you assign the Shopify Standard Product Taxonomy category, "set the product category", "categorize the catalog", or fill the category / taxonomy metafields: the admin "Category metafields" panel (Color, Size, Material, Caffeine content), a.k.a. product category attributes / taxonomy attributes, including the attributes a Google feed asks for. Covers bulk classification, discovering a category's attributes and allowed values, minting the reserved value metaobjects, and writing them sa...
Scanned 8/31/2026
Install to Claude Code
npx -y skills add kgelster/awesome-ecom-skills --skill shopify-category-taxonomy --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Shopify Category Taxonomy?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/kgelster-shopify-category-taxonomy)More formats (shields.io, HTML) on the badges page.
---
name: shopify-category-taxonomy
description: >-
Use when you assign the Shopify Standard Product Taxonomy category, "set the
product category", "categorize the catalog", or fill the category / taxonomy
metafields: the admin "Category metafields" panel (Color, Size, Material,
Caffeine content), a.k.a. product category attributes / taxonomy attributes,
including the attributes a Google feed asks for.
Covers bulk classification, discovering a category's attributes and allowed
values, minting the reserved value metaobjects, and writing them safely. Also:
"category metafields show blank/null", "my Color attribute won't save". Not
for bulk CSV data loads (Matrixify can't do this; see shopify-matrixify for
bulk data).
compatibility: >-
Shopify Admin API 2025-07 (GraphQL). Needs read_products + write_products AND
read_metaobjects + write_metaobjects. The metaobjects scopes are the #1 trap:
without read_metaobjects the values return null and look unwritable.
---
# 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:
```bash
export SHOPIFY_STORE="your-store.myshopify.com"
export SHOPIFY_ACCESS_TOKEN="<your Admin API 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.
**Toolkit preflight.** Lane B rides on the Shopify CLI: run `shopify version`
first and install it if missing. For the full Admin GraphQL schema and
validated execution, pair this skill with Shopify's official AI toolkit
plugin. In Claude Code, check `claude plugin list`; if it isn't there:
```bash
claude plugin marketplace add Shopify/Shopify-AI-Toolkit
claude plugin install shopify-plugin@shopify-ai-toolkit
```
Recommended, not required: Lane A needs only curl and a token. The toolkit
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.
2. **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.
```graphql
# 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--<attribute>"`) with a `label` and a
`taxonomy_reference` pointing at a global `TaxonomyValue`. The chain:
```
Product
└─ metafield shopify.<attribute> (list.metaobject_reference)
└─ value: ["gid://shopify/Metaobject/…"]
└─ Metaobject type: shopify--<attribute> 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`.
2. **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.
3. **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.
4. **GIDs are store-specific.** Metaobject GIDs minted in one store do not port
to another. Re-mint per store.
5. **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.**
```graphql
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.
```graphql
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.
```graphql
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.**
```graphql
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: []`.
```graphql
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](https://shopify.dev/docs/api/admin-graphql) and the
metafields/metaobjects docs before trusting a version-specific claim. The
category IDs and taxonomy values come from Shopify's
[Standard Product Taxonomy](https://github.com/Shopify/product-taxonomy): the
canonical source to verify an ID against; never guess one.
Read-only re-verification a stranger can run (no writes):
```bash
# 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.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!