← /skills/ · skill·v 1·last edit May 14, 2026

How to standardize Shopify product tags in Claude Code with AI

Give Claude Code a tag taxonomy and your Shopify product files. It writes a validator, fixes every product's tags in place, and iterates until all pass. promptingshopify·2.8 kb prompt·works with Claude Code · Claude · Cursor · Codex · Antigravity

Search "claude code shopify" and Google suggests claude code shopify skill, claude code shopify integration, claude code shopify mcp. People want to put Claude Code to work on their catalog. The first task that bites them is tags: thousands of products, drift across every dimension. Summer and summer and SUMMER. Five different ways to spell "womens." A product with no season tag. A product tagged cozy-vibes that exists nowhere else in the catalog and breaks every filter.

The popular advice is "ask the AI to tag your products." That works on one product. It fails at catalog scale because the AI is generating free-form tags, not picking from your master list. So it invents new ones, casing drifts, and you find out three months later when a category page is missing half its products.

This skill closes the loop. You give Claude Code a taxonomy file (your canonical tags, grouped by category) and a directory of Scratch-pulled product files. Claude writes a validator that enforces taxonomy membership, case-exact canonical spelling, deduping, and per-category coverage. Then it edits every product's tags field in place, runs the validator, and iterates until every record passes. Scratch surfaces the diff for review. You approve. You ship. The validator does the rule-checking no human can do at scale. The human spends judgment only on the calls that genuinely need it: is this dress really womens, or could it go unisex?

The prompt: standardize Shopify tags against a taxonomy

You are standardizing the tags array on a set of Shopify product files
Scratch pulled from a catalog. The goal is consistent, validated tags
drawn from a master taxonomy, edited in place, ready for human review
in Scratch's diff view.

INPUTS:
- TAXONOMY: path to taxonomy.json. A JSON object where each key is a
  category name and each value is an array of allowed tag strings
  (canonical lowercase).
- PRODUCTS_DIR: path to a directory containing one .json file per
  product (Shopify GraphQL Admin shape: tags is an array of strings).

WORKFLOW (validator-first, non-negotiable order):

1. Read TAXONOMY. Note its categories and the canonical spelling of
   every allowed tag.

2. Write a validator at .scratch/validators/standardize-tags.py.
   Given TAXONOMY and a glob of product .json files as arguments,
   for every product file it must exit 0 only when ALL of:
   - The product's "tags" field is a JSON array of strings (not a
     comma-separated string, not null).
   - Every tag in that array appears as an exact-string match in the
     union of taxonomy values (case-sensitive against the canonical
     spelling).
   - No duplicate tag within a single product (case-insensitive).
   - For every category in the taxonomy, the product has at least one
     tag drawn from that category's allowed values.
   On failure: exit 1 with one diagnostic line per broken rule,
   prefixed with the product file's basename.

   Do not edit any product file until this validator exists on disk.

3. For each product file in PRODUCTS_DIR (excluding taxonomy.json),
   edit the "tags" array in place:
   - Drop any tag not present in the taxonomy (these are noise).
   - Rewrite remaining tags to the taxonomy's canonical lowercase
     spelling.
   - Dedupe within the array.
   - For every taxonomy category the product is missing, add at least
     one tag drawn from that category's allowed values, inferred from
     the product's title, descriptionHtml, productType, and vendor.
     When multiple values plausibly apply (e.g. a year-round product
     spanning all seasons), include all that apply.
   - Preserve every other field of the JSON exactly. Preserve the
     file's JSON formatting style (indentation, trailing newline).
   Do not write a stdout artifact. Do not produce a summary JSON. The
   in-place edits to the .json files are the only output.

4. Run the validator: `python3 .scratch/validators/standardize-tags.py
   TAXONOMY 'PRODUCTS_DIR/*.json'`. If exit code is non-zero, read the
   diagnostics, refine the offending tag arrays, and re-run. Iterate
   until exit 0.

5. Print one line: the count of products edited and "VALIDATED · review
   diff in Scratch". Do not print the validator source, the edited
   files, or the diagnostics to chat. Everything lives on disk for
   Scratch to surface.

How Claude Code standardizes your Shopify product tags

  1. Claude reads your taxonomy file and notes every category and the canonical spelling of every allowed tag.
  2. Claude writes a Python validator to .scratch/validators/standardize-tags.py that enforces four rules: array shape, taxonomy membership with case-exact spelling, no duplicates, and at least one tag per category.
  3. Claude opens each product .json file and edits the tags array in place: drops non-taxonomy tags, rewrites to canonical casing, dedupes, and adds tags from any missing category by reading the product's title, description, type, and vendor.
  4. Claude runs the validator against the edited files and iterates until every product passes. Scratch's diff view now shows you exactly what changed in each record, and you approve and ship through Scratch.

The validator Claude Code writes to enforce your tag taxonomy

Your AI writes its own validator when you run this against your records. This is shown for reference and to prove that the validator-first path produces something coherent.

#!/usr/bin/env python3
"""Validate product .json files against a tag taxonomy.

Usage:
    python3 standardize-tags.py <taxonomy.json> '<products-glob>'

Exit 0 iff every product file satisfies all of:
- "tags" is a JSON array of strings (not a CSV string, not null).
- Every tag is an exact-string match in the union of taxonomy values
  (case-sensitive against the canonical spelling).
- No duplicate tag within a single product (case-insensitive).
- For every category in the taxonomy, the product has at least one tag
  drawn from that category's allowed values.

On failure: exit 1 with one diagnostic line per broken rule, prefixed
with the product file's basename.
"""

import glob
import json
import os
import sys


def main() -> int:
    if len(sys.argv) != 3:
        sys.stderr.write(
            "usage: standardize-tags.py <taxonomy.json> '<products-glob>'\n"
        )
        return 2

    taxonomy_path, products_glob = sys.argv[1], sys.argv[2]

    with open(taxonomy_path, "r", encoding="utf-8") as f:
        taxonomy = json.load(f)

    if not isinstance(taxonomy, dict):
        sys.stderr.write("taxonomy.json: top-level must be an object\n")
        return 2

    categories = {cat: set(vals) for cat, vals in taxonomy.items()}
    canonical_union = set()
    for vals in categories.values():
        canonical_union.update(vals)

    paths = sorted(glob.glob(products_glob))
    taxonomy_abs = os.path.abspath(taxonomy_path)
    paths = [p for p in paths if os.path.abspath(p) != taxonomy_abs]

    diagnostics: list[str] = []

    for path in paths:
        base = os.path.basename(path)
        try:
            with open(path, "r", encoding="utf-8") as f:
                product = json.load(f)
        except (OSError, json.JSONDecodeError) as exc:
            diagnostics.append(f"{base}: cannot read/parse JSON ({exc})")
            continue

        if not isinstance(product, dict) or "tags" not in product:
            diagnostics.append(f"{base}: missing 'tags' field")
            continue

        tags = product["tags"]

        if not isinstance(tags, list) or not all(
            isinstance(t, str) for t in tags
        ):
            diagnostics.append(
                f"{base}: 'tags' must be a JSON array of strings"
            )
            continue

        for t in tags:
            if t not in canonical_union:
                diagnostics.append(
                    f"{base}: tag {t!r} is not in the taxonomy "
                    f"(canonical spelling required)"
                )

        seen_lower: dict[str, str] = {}
        for t in tags:
            key = t.lower()
            if key in seen_lower:
                diagnostics.append(
                    f"{base}: duplicate tag (case-insensitive): "
                    f"{seen_lower[key]!r} vs {t!r}"
                )
            else:
                seen_lower[key] = t

        tag_set = set(tags)
        for cat, allowed in categories.items():
            if not (tag_set & allowed):
                diagnostics.append(
                    f"{base}: missing tag from category {cat!r} "
                    f"(allowed: {sorted(allowed)})"
                )

    if diagnostics:
        for line in diagnostics:
            sys.stderr.write(line + "\n")
        return 1

    return 0


if __name__ == "__main__":
    sys.exit(main())

Example: cleaning up a messy Shopify product tag

The taxonomy used for the test:

{
  "season": ["spring", "summer", "fall", "winter"],
  "audience": ["mens", "womens", "kids", "unisex"],
  "category": ["pants", "shirts", "dresses", "coats", "accessories"],
  "material": ["linen", "cotton", "wool", "silk", "synthetic"]
}

A product Scratch pulled with the kind of drift every real Shopify catalog has:

Before (01-linen-drawstring-pants.json):

{
  "id": "gid://shopify/Product/8431001100001",
  "handle": "linen-drawstring-pants",
  "title": "Linen Drawstring Pants",
  "descriptionHtml": "<p>Lightweight breathable linen pants with a relaxed drawstring waist. Cut for women, designed for hot weather.</p>",
  "vendor": "Maple Coast",
  "productType": "Pants",
  "status": "ACTIVE",
  "tags": ["Summer", "WOMENS", "Linen", "summer"]
}

Tag-field drift: two season tags only because Summer and summer are spelled differently, casing inconsistent on three of four tags, and no tag at all from the category group (no pants).

After Claude edited it in place:

- "tags": ["Summer", "WOMENS", "Linen", "summer"]
+ "tags": ["summer", "womens", "pants", "linen"]

Every other field of the JSON was preserved byte-for-byte.

Validator output against the edited file:

$ python3 .scratch/validators/standardize-tags.py \
    test-records-edited/taxonomy.json \
    'test-records-edited/*.json'
$ echo $?
0

A second product in the same run, 02-cotton-tshirt-mens.json, started with ["mens", "shirts"] and a description that called it a "year-round wardrobe staple." Claude inferred the year-round case correctly and the validator passed on ["mens", "shirts", "spring", "summer", "fall", "winter", "cotton"].

Edge case: a Shopify product with four kinds of tag drift

Realistic drift composed into one record (99-adversarial.json):

{
  "title": "Silk Summer Maxi Dress",
  "descriptionHtml": "<p>A flowing silk maxi dress, cut from lightweight silk. Made for warm weather events. Hand-wash only.</p>",
  "productType": "Dresses",
  "tags": ["Cream", "maxi", "Summer-2026", "silk", "silk"]
}

Three tags not in the taxonomy (Cream, maxi, Summer-2026), a duplicate (silk/silk), no audience tag, no category tag.

Validator before fix (run against the untouched file):

99-adversarial.json: tag 'Cream' is not in the taxonomy (canonical spelling required)
99-adversarial.json: tag 'maxi' is not in the taxonomy (canonical spelling required)
99-adversarial.json: tag 'Summer-2026' is not in the taxonomy (canonical spelling required)
99-adversarial.json: duplicate tag (case-insensitive): 'silk' vs 'silk'
99-adversarial.json: missing tag from category 'season' (allowed: ['fall', 'spring', 'summer', 'winter'])
99-adversarial.json: missing tag from category 'audience' (allowed: ['kids', 'mens', 'unisex', 'womens'])
99-adversarial.json: missing tag from category 'category' (allowed: ['accessories', 'coats', 'dresses', 'pants', 'shirts'])
$ echo $?
1

Seven distinct diagnostics across four rule classes. Claude reads them, edits the file, re-runs.

After Claude's fix:

- "tags": ["Cream", "maxi", "Summer-2026", "silk", "silk"]
+ "tags": ["silk", "summer", "womens", "dresses"]

Cream and maxi dropped (not in taxonomy). Summer-2026 rewritten to canonical summer. Duplicate silk deduped. womens and dresses added by inference from "maxi dress" and productType: "Dresses".

Validator after fix:

$ python3 .scratch/validators/standardize-tags.py taxonomy.json 99-adversarial.json
$ echo $?
0

The audience inference (womens versus unisex) is the one call only you can make. Scratch's diff view surfaces it next to the other changes; you override before shipping if the dress is genuinely unisex.

Why the validator enforces a Shopify tag taxonomy this way

How to standardize tags in your own Shopify catalog

  1. Pull your Shopify products into Scratch. They land in scratch/shopify/products/ as one .json per product in Shopify GraphQL Admin shape.
  2. Create scratch/shopify/taxonomy.json with your master taxonomy: an object whose keys are your categories (season, audience, etc.) and whose values are arrays of canonical lowercase tag strings.
  3. Open Claude Code at your Scratch project root and paste the skill prompt above. Tell Claude your TAXONOMY path and your PRODUCTS_DIR path.
  4. Claude writes the validator to .scratch/validators/standardize-tags.py, edits every product's tags in place, and runs the validator until everything passes.
  5. Open Scratch. Review the diff view. Override anything where Claude's category inference was wrong (the audience call is the most common one to revisit).
  6. Approve and ship. Scratch publishes the standardized tags back through the Shopify Admin API.

Try this on a real project.

Curtis runs intro calls personally. Bring a refresh, a migration, or anything that feels sticky. We'll work through whether Scratch fits.

Talk to Curtis → or start with Scratch