When a shopper asks an AI assistant for “a men’s waterproof hiking jacket in green, size large,” the assistant does not read every jacket description on the internet. It sends a query to a catalog with filters attached: category, target gender, color, size. Products that match the filters come back. Products that do not match, or that have no value to test, do not.
Fig. 01 A filter is a yes or no test
The same shopper request, run against two products. The assistant tests four attributes. A product with nothing to test fails the test.
Querymen’s waterproof hiking jacket, green, size large
Jacket A: attributes set by the merchant
In the candidate set, goes on to ranking
Jacket B: same product, attributes never set
Never ranked, and nobody is told
Illustrative products. Inferred values are Shopify’s machine-learning guesses for empty fields, which Shopify itself describes as having “varying accuracy”. A wrong guess or an empty field fails the filter the same way.
On Shopify, those filterable values are category attributes. They are the most direct lever a merchant has on whether a product is chosen by agents, and most catalogs have never set them. This post covers why they matter, the one rule that trips people up, how to set them by hand, and the API recipe for doing it across thousands of products.
What category attributes are
Every Shopify product can carry a category from the Standard Product Taxonomy. Once a product has a category, a set of attributes becomes available for it: a jacket gets color, material, size, target gender, age group, activity, and so on. A coffee grinder gets a different set. The 2026-05 taxonomy release added roughly 2,000 categories and 4,000 attributes, so the vocabulary is broad.
These attributes are stored as metafields in the reserved shopify namespace. They are what the Catalog exposes to ChatGPT, Copilot, Gemini and Meta, and what the search_catalog tool filters on. They are also what Anthropic’s Claude Commerce Agents reference ranks on, alongside title, brand, category, option values and short description, according to an analysis of that blueprint.
Why explicit values beat Shopify’s guesses
When an attribute is empty, Shopify fills it in with machine learning. The company’s own explainer, What is Shopify Catalog, describes that inference as having “varying accuracy,” and the Global Catalog API marks inferred values as such in responses.
A filter is a yes-or-no test. If the inferred material on your merino sweater is “wool blend” and the shopper asked for “merino,” the sweater fails the test and is never ranked. If the inferred target gender is wrong, same result. You do not get told. The product just does not appear.
An explicit value is one you chose from Shopify’s list. Every agent reads it the same way. That is the whole argument: explicit beats inferred because explicit is deterministic and inferred is a guess you did not review. (The five ranking factors Shopify names, and how inference fits into them, are in How Shopify Catalog ranks your products.)
The rule that trips people up: values must match the taxonomy
Category attributes are not free text. Each attribute has a fixed list of allowed values per category. Color has a list. Material has a list. “Forest green” is not on the color list; “Green” is. “Merino wool” is not on the material list; “Wool” is.
This matters in two directions. When you set values by hand, you pick from a dropdown, so you cannot get it wrong. When you set them through the API, you have to reference the taxonomy value by its ID, and a value that does not exist for that category is rejected. Any tool that writes attributes at scale has to map its proposals onto the allowed list first. If it cannot find an exact match, the correct behavior is to leave the field alone and tell you, not to force the closest word in.
Fig. 02 Values must match the taxonomy exactly
Three proposed values for the same two attributes, and what a tool that writes at scale should do with each.
A value that does not exist for the category is rejected by the API. In the admin you pick from a dropdown, so this only bites tools. One naming detail: the color attribute’s key is color-pattern, not color.
One naming detail: the color attribute’s key is color-pattern, not color. It holds both color and pattern references. If you audit your own metafields and see no shopify.color, that is why.
Setting them one product at a time
In the admin, open a product, find the Category field, and pick the most specific category that fits. The attribute fields appear below it. Choose values from each dropdown and save. That is the whole process. For a store with 40 products, it is an afternoon and you should just do it.
For a store with 400 or 4,000 products it is not realistic, which is where the rest of this post comes in.
The API recipe for setting them at scale
This is the write path we verified on a live store and now use in Bot It. It uses three Admin GraphQL mutations and one query. The two prerequisites cost more time than the mutations, so read them first.
Fig. 03 The write path, one attribute on one product
Two prerequisites, then one query and three Admin GraphQL mutations. The prerequisites cost more time than the mutations.
Step 1, query
Look up allowed values
node(id:) on the category GID returns each attribute and its value list. Keep the value’s GID.
Step 2, mutation
Enable the definition
standardMetaobjectDefinitionEnable for shopify– plus the handle. TAKEN means already on, treat as success.
Step 3, mutation
Upsert the value entry
metaobjectUpsert with a label and taxonomy_reference. Color uses color_taxonomy_reference, a list.
Step 4, mutation
Set the metafield
metafieldsSet namespace shopify, key = attribute handle, type list.metaobject_reference.
Revert: metafieldsDelete with the same owner, namespace and key. The value entry stays and is reused across products.
Four calls per attribute per product. Across a catalog that is thousands of calls plus the mapping step in Fig. 02, which is the chain Bot It runs for you.
Prerequisites
- The product must already have a category. If it does not, the final metafield write fails with “Owner subtype does not match,” because Shopify has no way to know which attribute set applies. Set the category first.
- Your app or token needs metaobject scopes:
read_metaobjects,write_metaobjects,read_metaobject_definitionsandwrite_metaobject_definitions, in addition to product scopes. Without them Shopify does not return a permissions error. It returns a not-found error that looks like the definition does not exist. We lost a day to this.
Step 1: look up the allowed values
Query the product’s category node to get its attributes and their allowed values. Note that this goes through node(id:) with the category GID.
query taxonomyCategory($id: ID!) {
node(id: $id) {
... on TaxonomyCategory {
name
isLeaf
attributes(first: 50) {
edges { node {
... on TaxonomyChoiceListAttribute {
id name
values(first: 250) { edges { node { id name } } }
}
} }
}
}
}
}
Find the attribute you want (say Material) and the value you want (say Cotton). Keep the value’s GID. It will look like gid://shopify/TaxonomyValue/....
Step 2: enable the standard metaobject definition
Each attribute has a Shopify-provided metaobject definition named shopify-- plus the attribute handle. Enable it once per store.
mutation {
standardMetaobjectDefinitionEnable(type: "shopify--material") {
metaobjectDefinition { id type }
userErrors { field message code }
}
}
A TAKEN error means it was already enabled. Treat that as success. Not every attribute has a standard definition. Pattern, for example, did not on the store we tested, and those have to be set in the admin.
Step 3: upsert a metaobject for the value
Create or reuse the metaobject entry that represents the value. The handle is a slug of the value name; the fields are a label and a reference to the taxonomy value.
mutation metaobjectUpsert($handle: MetaobjectHandleInput!, $metaobject: MetaobjectUpsertInput!) {
metaobjectUpsert(handle: $handle, metaobject: $metaobject) {
metaobject { id handle }
userErrors { field message code }
}
}
# variables
{
"handle": { "type": "shopify--material", "handle": "cotton" },
"metaobject": { "fields": [
{ "key": "label", "value": "Cotton" },
{ "key": "taxonomy_reference", "value": "gid://shopify/TaxonomyValue/..." }
] }
}
Color is the exception. Its type is shopify--color-pattern, and the reference field is color_taxonomy_reference, which is a list, so the value is a JSON array of one or more TaxonomyValue GIDs.
Step 4: set the metafield on the product
mutation metafieldsSet($metafields: [MetafieldsSetInput!]!) {
metafieldsSet(metafields: $metafields) {
metafields { id key }
userErrors { field message code }
}
}
# variables
{ "metafields": [ {
"ownerId": "gid://shopify/Product/...",
"namespace": "shopify",
"key": "material",
"type": "list.metaobject_reference",
"value": "[\"gid://shopify/Metaobject/...\"]"
} ] }
The key is the attribute handle (material, size, target-gender, age-group, color-pattern). The value is a JSON-encoded array of the metaobject GIDs from step 3. After this write, the value shows up in the admin dropdown exactly as if you had picked it by hand, and it flows to Catalog on the next sync.
Reverting
To put an attribute back to unset, delete the metafield with metafieldsDelete using the same owner, namespace and key. The metaobject entry can stay; it is reusable across products.
What Bot It does with this
The recipe above is four calls per attribute per product. Across a catalog it is thousands of calls, plus the mapping step that decides what value to propose in the first place. Bot It handles the whole chain:
- The audit finds every product with a category but empty attributes, and every product with no category.
- A vision pass looks at the product’s images and copy and proposes values, constrained to the allowed list for that category so the proposal can actually be written.
- Proposals are matched strictly against the taxonomy. A value that does not match exactly is shown as “set in admin” rather than forced.
- Fixes apply at the automation level you pick: review everything, semi-auto, or full auto. Category and category attribute fixes are capped at semi-auto by design, because category can affect US tax treatment and you should see those before they land.
- If a product has no category yet, the attribute fix waits until the category fix is applied, then runs.
- Every write goes into a changelog and is revertible with one click. Revert deletes the metafield, exactly as above.
On the free plan you see every gap and can approve fixes one at a time. On Pro the queue drains on its own at whatever level you set.
The practical next step: pick ten products you care about, open each one in the admin, and look at the category and attribute fields. If they are empty, you now know what an assistant is working from. Fill them by hand for those ten, or install Bot It and let the audit show you how far the gap extends across the rest.


