| name | shopify-seo-metadata |
| description | Use when a store has missing SEO titles or meta descriptions and you want to backfill them: "backfill missing SEO titles/descriptions", "meta description sweep", "SEO meta backfill across products/collections/pages/blog posts", "missing page titles", "fix blank search-appearance metadata", "generate SEO meta for products". Safe-mode: fills only the empty search-appearance fields (SEO title, meta description) on products, collections, pages, and blog articles, never touching human-written values. Not for image alt text (use shopify-alt-text) or JSON-LD (use shopify-json-ld). |
| compatibility | Requires Shopify Admin API (custom-app token or Shopify CLI 3.x); GraphQL written against Admin API 2025-07. Minimum scopes: read_products, write_products (products/collections), plus read_content, write_content for pages and blog articles. Generation needs an LLM API key. |
Shopify SEO Metadata
Safe-mode backfill of missing search-appearance metadata (the SEO title and
meta description Google shows in the results snippet) across products,
collections, pages, and blog articles. The core principle: fill blanks, never
overwrite. A merchant's hand-written meta title is worth more than anything a
model generates; this skill only touches fields that are empty.
Sister skill shopify-alt-text runs the same fill-missing backfill for
product-image alt text; shopify-json-ld handles structured data. To first
measure how much metadata is missing store-wide, shopify-catalog-audit is the
FIND stage that scopes this FIX.
Store access
Every skill that touches the Admin API opens with this stanza (minimum scopes
for this skill below). Two lanes; pick one.
Lane A: custom-app token (scriptable). In Shopify admin: Settings → Apps
and sales channels → Develop apps → create an app → grant the scopes below, then
install and copy the Admin API access token. Export it; never write it to a
committed file:
export SHOPIFY_STORE="your-store.myshopify.com"
export SHOPIFY_ACCESS_TOKEN="<your Admin API access token>"
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 } }"}'
Lane B: Shopify CLI OAuth (no stored token). shopify store auth --store $SHOPIFY_STORE --scopes read_products,write_products,read_content,write_content
then shopify store execute to run a validated operation. Good for token-less
stores where the owner logs in interactively.
Minimum scopes for this skill: read_products + write_products cover
products and collections (both live on the products scope). Pages and blog
articles need read_content + write_content. Scope only what the run touches.
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:
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
Every GraphQL block in references/queries-and-prompts.md
is a copy-and-adapt starting point. Write throwaway code per engagement against
the current schema, run it, discard it. Two rules keep a metadata backfill safe.
Safe mode is the whole job
State this out loud before any run, and treat it as non-negotiable:
- Fill missing ONLY. Never overwrite a value that already exists. For each
entity, read
seo { title description } (products/collections) or the
global.title_tag / global.description_tag metafields (pages/articles)
FIRST. A field with a non-empty value is skipped, full stop. An "overwrite
everything" mode is a different, deliberate job: it is never the default and
a stranger running this skill does not get it for free.
- Fill the two fields independently. A product can have a title but no
description. Generate and write only the blank one; leave the populated one
byte-for-byte untouched.
Preview first, then mutate
Never open a bulk write without a number in front of you.
- Run the read-only COUNT (per entity, in the reference) of how many items
have a missing field. This is the exact count the mutation loop will act
on, using the same skip logic.
- Sanity-check the number against expectation. A 40-product store reporting
3,000 items needing SEO means the skip logic is wrong or you are pointed at
the wrong store. A count far above expectation means STOP and re-scope:
do not proceed into a mutation run to "see what happens." An unscoped write
over a catalog is not free to undo.
- Only after the count is sane do you generate and write.
Preflight the LLM key with a real completion
Before a bulk run, send the generation model a real 1-token completion, not
a models.list() / model-listing call. A key with read access to the model list
can still fail on an actual completion (billing lapsed, org quota, wrong
project). A dead key discovered mid-run burns the whole queue as failed rows and
wastes the crawl. One tiny completion up front is the cheapest insurance there
is.
Generate, write, verify: per entity
For each entity type the shape is identical; only the read query, the write
mutation, and the content source field differ (all four are in the reference):
- Products: source = product title + body HTML. Write via
productUpdate
with input.seo { title description }.
- Collections: source = collection title + description. Write via
collectionUpdate, same seo input.
- Pages: source = title +
bodySummary. SEO lives in global.title_tag
(single line) and global.description_tag (multi line) metafields; write via
metafieldsSet.
- Blog articles: source = title +
summary (+ parent blog title for
context). Same global.*_tag metafields, same metafieldsSet.
The source data is the ceiling on output quality. Products generate from
body HTML: if a bulk description cleanup or rewrite just ran on this catalog
(e.g. via shopify-catalog-cleanup), confirm its post-edit re-scan passed
before you generate, or the products whose markup the transform mangled get
that garbage baked into fresh meta at scale. Enrichment does not fix bad
source data; it launders it into new fields.
Log every write (resource id, field, old value, new value, status) to a CSV or
newline log as you go. That log is your evidence and your retry list.
Verify AFTER the write: via the Admin API, never the storefront
A mutation returning no userErrors is a claim, not proof. Confirm it:
- After the run, re-read
seo { title description } (or the global.*_tag
metafields) for 2–3 written resources through
admin/api/2025-07/graphql.json and confirm the value matches what you wrote.
- Read back through the Admin API, not the storefront. The storefront
<meta>
tags are CDN-cached and lie about freshness: a stale edge page will show the
old (or empty) tag long after the write landed, sending you chasing a
non-bug. The Admin API is the source of truth.
Prompt guidance
Quality of the generated meta depends on three things the model can't infer:
- Feed the store's positioning into every prompt. A one-paragraph brand
description (who they sell to, the voice, the category) is the difference
between on-brand copy and generic filler. A thin or wrong description produces
off-brand meta at scale: write a real one before a bulk run.
- Length targets. SEO title ~50–60 characters (Google truncates past ~60);
meta description ~150–160. Models cannot count characters reliably, so
validate length in code and re-prompt anything over the limit rather than
trusting the model to obey.
- No keyword stuffing. Front-load the one primary keyword a buyer would
actually search, write for a human reading a result, and stop. Repeated
keywords and ALL-CAPS promo language read as spam and help nothing.
Full query/mutation/prompt detail:
references/queries-and-prompts.md.
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 the seo field
and the resource mutations against
shopify.dev before trusting a
version-specific claim. Read-only re-verification a stranger can run, confirming
the token reaches the store and the seo field resolves on products:
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: 3) { nodes { title seo { title description } } } }"}'
Canonical docs: the seo field on resources and the metafield model at
Admin GraphQL. This skill captures
the operational safe-mode doctrine the reference doesn't; it is not a
replacement for it.