Metaobject Migration Blueprint: Replace Tag Sprawl with Scalable Shopify Filtering
Migrate Shopify tags and legacy metafields to metaobjects with GraphQL mapping, canonical filter keys, and storefront-safe filtering.
Tag-based faceting collapses when your catalog reaches scale. Synonyms drift, filters fragment, and merchandising becomes spreadsheet maintenance instead of system governance.
Where this breaks on scaling stores
- One intent creates multiple tags (
organic cotton,organic-cotton,org-cotton). - Filter values exceed practical UI limits and hurt conversion paths.
- Headless storefront filtering becomes unstable because taxonomy is not relational.
- Teams cannot safely enforce naming with manual tag edits.
Migration architecture
Use metaobjects as the controlled taxonomy layer and map products to canonical filter keys.
Define the taxonomy object
UI path: Settings > Custom data > Metaobjects.
mutation CreateMaterialDefinition {
metaobjectDefinitionCreate(
definition: {
name: "Material"
type: "material"
fieldDefinitions: [
{
key: "label"
name: "Label"
type: "single_line_text_field"
required: true
}
{
key: "filter_key"
name: "Filter key"
type: "single_line_text_field"
required: true
capabilities: {
adminFilterable: { enabled: true }
}
}
{
key: "synonyms"
name: "Synonyms"
type: "list.single_line_text_field"
}
]
}
) {
metaobjectDefinition {
id
type
}
userErrors {
field
message
code
}
}
}
Seed normalized entries
mutation CreateMaterialOrganicCotton {
metaobjectCreate(
metaobject: {
type: "material"
fields: [
{ key: "label", value: "Organic Cotton" }
{ key: "filter_key", value: "organic-cotton" }
{ key: "synonyms", value: "[\"organic cotton\",\"org cotton\"]" }
]
}
) {
metaobject {
id
handle
type
}
userErrors {
field
message
code
}
}
}
Product mapping pattern
Use dual metafields on the product:
custom.material_refsaslist.metaobject_referencecustom.material_keysaslist.single_line_text_fieldfor storefront filters
mutation AttachMaterials {
metafieldsSet(
metafields: [
{
ownerId: "gid://shopify/Product/1234567890"
namespace: "custom"
key: "material_refs"
type: "list.metaobject_reference"
value: "[\"gid://shopify/Metaobject/111\",\"gid://shopify/Metaobject/112\"]"
}
{
ownerId: "gid://shopify/Product/1234567890"
namespace: "custom"
key: "material_keys"
type: "list.single_line_text_field"
value: "[\"organic-cotton\",\"recycled-poly\"]"
}
]
) {
userErrors {
field
message
code
}
}
}
Storefront filter integration
Configure Search & Discovery
UI path: Apps > Search & Discovery > Filters > Add filter.
Select custom.material_keys as the filter source.
Query filters in Storefront API
query CollectionFilters($handle: String!, $filters: [ProductFilter!]) {
collection(handle: $handle) {
id
title
products(first: 24, filters: $filters) {
nodes {
id
handle
title
}
filters {
id
label
type
values {
id
label
count
input
}
}
}
}
}
Variables:
{
"handle": "mens-shirts",
"filters": [
{
"productMetafield": {
"namespace": "custom",
"key": "material_keys",
"value": "organic-cotton"
}
}
]
}
Controlled migration execution
Build one-time mapping script
// pseudo-logic for migration batch
for (const product of legacyProducts) {
const normalizedKeys = normalizeLegacyTags(product.tags, taxonomyMap);
await adminGraphql(`
mutation UpdateProduct($id: ID!, $keys: String!, $refs: String!) {
metafieldsSet(metafields: [
{ ownerId: $id, namespace: "custom", key: "material_keys", type: "list.single_line_text_field", value: $keys },
{ ownerId: $id, namespace: "custom", key: "material_refs", type: "list.metaobject_reference", value: $refs }
]) {
userErrors { field message code }
}
}
`, {
id: product.id,
keys: JSON.stringify(normalizedKeys),
refs: JSON.stringify(toMetaobjectIds(normalizedKeys))
});
}
Deprecate tags only after parity checks
- Compare collection counts by filter key between old and new systems.
- Validate no-impact on top SEO landing collections.
- Freeze tag editing permissions in merchant SOPs.
Technical Checklist
- Metaobject definitions created for every governed facet.
- Canonical
filter_keyformat enforced (kebab-case). - Product mapping done in batches with retry/idempotency.
-
material_refsandmaterial_keysboth populated. - Search & Discovery filters configured from canonical metafields.
- Legacy tag-based filter code removed after parity verification.
SEO Checklist
- Collection URLs remain stable during filter system migration.
- Canonical rules prevent indexing of low-value facet combinations.
- Facet labels align with real search demand language.
- Internal links from guides/category intros target canonical collection paths.
- Filtered pages that are not strategic remain non-indexed.