Most Shopify custom-data messes aren’t the platform’s fault. They come from eighteen months of adding a metafield every time someone asked for a new badge, until the product template reads from forty fields, three of them are called some version of “material”, and nobody remembers which one the theme actually uses.
The fix isn’t more fields. It’s deciding, once, what kinds of things your catalog contains, how they relate to each other, and who’s allowed to change them. This guide covers the building blocks, five design rules, the relationship patterns Shopify supports, three reference architectures you can copy, the limits that bite at scale, how to run it day to day, and a migration path for a store that’s already tangled.
The building blocks
Shopify’s custom data comes down to a handful of parts. If you’ve designed a database before, Shopify’s own developer docs map them onto concepts you know:
| Shopify part | Database equivalent | What it is |
|---|---|---|
| Resource | Built-in table | Product, Variant, Collection, Customer, Order, Page |
| Metafield definition | Column on a built-in table | A typed field added to a resource, such as custom.fabric_weight on Product |
| Metaobject definition | Custom table | A new kind of record, such as designer or size_guide |
| Metaobject field | Column on a custom table | bio on Designer |
| Metaobject entry | Row | The designer “Ana Ruiz” |
| Reference type | Foreign key | product_reference, metaobject_reference, and their list. versions |
| Validation | Constraint | Required, min/max, regex, allowed metaobject types |
| Capability | Feature flag on a definition | Draft/active status, translations, web pages |
shopify.app.toml |
Migration file | Where an app declares the definitions it owns |
Every field, on a metafield or a metaobject, has a type (text, number, dimension, rich text, file, reference and so on), and Shopify validates values against it. That typing is the main reason to use these tools instead of tags or JSON blobs: the theme, the admin and the APIs all know what a value is.
Five design rules
1. Model the domain, not the page
“Designer” is a thing in your business. “Homepage designer carousel” is a layout. Define the thing, with the fields that describe it, and let theme sections decide how to show it. Definitions named after page sections get duplicated the moment the same data needs to appear somewhere else.
2. Reuse before you create
Check what exists first. Shopify’s standard product taxonomy gives categorised products category metafields (fabric, color, neckline, sleeve length and many more), backed by metaobject entries Shopify provides. There are also standard metaobject definitions for common cases. Standard definitions don’t count toward your store’s definition limit, and other parts of Shopify understand them. Build custom definitions for what’s genuinely yours.
3. Link with reference types, never text
A relationship belongs in a reference field: metaobject_reference for one, list.metaobject_reference for many, product_reference for products. Shopify’s docs warn specifically against storing a handle or ID in a text field to fake a link, because Liquid and the Storefront API can’t resolve it. A reference field gives the theme the object itself, with no extra lookups.
4. Keep field keys stable
Display names are free to change; field keys aren’t. The key (chest_cm, care, fit_notes) is what Liquid reads, what the API writes, and what every CSV column maps to. Renaming a key means updating every template, script and import file that uses it. Pick keys that describe the data in plain terms, and don’t encode presentation in them (fit_notes, not fit_notes_sidebar).
5. Decide who edits what
Ownership is part of the design:
- Merchant-owned definitions (created in the admin or through the API with an ordinary type such as
size_guide) are fully open: editable in the admin and by any app with the right access. - App-owned definitions use the reserved
$app:prefix (declared asappin TOML). The app controls the structure, and the admin can be limited to read-only.
Data that staff maintain day to day should be merchant-owned. Data an app depends on for its own logic (configuration, mappings) is safer app-owned. App-owned metaobjects are also the only ones a Shopify Function can read in its input query.
Relationship patterns
| Relationship | Pattern | Example |
|---|---|---|
| One-to-one | metaobject_reference or product_reference |
A product’s custom.size_guide points at one chart |
| One-to-many | list.metaobject_reference (up to 1,024 entries) |
A product’s custom.highlights lists several highlight entries |
| Many-to-many | A list of references on one side | Products list their highlights; each highlight is used by many products |
| Many-to-many with data | An intermediate metaobject with two references plus its own fields | recipe_ingredient: recipe, ingredient, quantity, sort order |
| Mixed content | mixed_reference or list.mixed_reference |
A landing page’s content blocks, each a different block type |
Shopify’s recommendation for many-to-many is the simple one: a list of references on the parent. Reach for an intermediate metaobject only when the relationship itself carries data, because it costs you: every read goes one level deeper, admin editing gets clunkier, and on headless builds the extra nesting can run into Storefront API query limits.
Three reference architectures
These are starting points we use on client stores. Each one keeps the number of definitions small and puts every relationship in a reference field.
Apparel
text
Product
├─ category metafields (standard) fabric, color, neckline, sleeve length
├─ custom.size_guide → size_guide metaobject_reference
├─ custom.care → care_instructions metaobject_reference
└─ custom.fit → fit_type metaobject_reference
size_guide
├─ title, fit_notes text
├─ size_chart file_reference (image)
└─ rows → size_chart_row list.metaobject_reference (optional)
size_chart_row
└─ size, chest_cm, waist_cm, length_cm text / number_decimal
Twelve care instructions and a few dozen size guides serve thousands of products. Fit type is a metaobject rather than free text so filters and badges can rely on a fixed set. Migrating size guides into metaobjects covers this model step by step.
B2B and industrial parts
text
Product
├─ custom.spec_sheet → spec_sheet metaobject_reference
├─ custom.certifications → certification list.metaobject_reference
└─ custom.compatible_with → Product list.product_reference
spec_sheet
├─ voltage, power, weight, dimensions typed measurement fields
└─ datasheet file_reference (PDF)
certification
├─ name, issuing_body, valid_until text / date
└─ certificate file_reference (PDF)
Shopify has typed measurement fields for most engineering units (voltage, power, pressure, frequency, weight, dimension and more), which keeps spec data sortable and comparable instead of strings like “230V”. Compatibility is a list of product references, so “works with” sections link to real products that can’t silently go stale; if a product is deleted, Shopify removes it from list references automatically.
Content-led brand
text
designer (web pages on: /pages/designers/{handle})
├─ name, bio, portrait, links
└─ referenced by Product.custom.designer
recipe (web pages on: /pages/recipes/{handle})
├─ title, intro, method text / rich text
├─ hero file_reference
└─ ingredients → recipe_ingredient list.metaobject_reference
recipe_ingredient (intermediate)
├─ product → Product product_reference
├─ quantity, unit, note text
└─ sort_order number_integer
Designers and recipes get their own URLs by turning on web pages for the definition (the renderable and onlineStore capabilities), rendered with a theme template. recipe_ingredient is the intermediate-metaobject pattern: the relationship between a recipe and a product carries its own data (quantity, note, order), so it gets its own definition.
Rendering constraints
Storage limits are generous. What you can render on one page is not. Shopify’s limits per page render in Liquid:
| Limit | Value |
|---|---|
Entries returned by a loop over a metaobject list, without paginate |
50 |
Entries per page with paginate |
250 |
| Unique entries of one metaobject type loaded per page | 1,000 |
Unique handles of one type looked up with metaobjects.TYPE[handle] per page |
20 |
Metaobject definitions accessed through metaobjects per page |
50 |
Design consequences:
- A list of 300 FAQ entries renders 50 unless the section paginates. Plan pagination, or narrower lists.
- Looking entries up by handle is fine on a product page (one or two lookups) and wrong in a collection grid (one lookup per product, over the limit of 20).
- Liquid themes can read your metaobjects whatever the storefront access setting is. Headless storefronts need the definition exposed to the Storefront API.
Planning for scale
| Limit | Value |
|---|---|
| Fields per metaobject definition | 40 |
| Merchant-created metaobject definitions per store | 128 on Basic, Shopify and Advanced; 256 on Plus |
| Metaobject definitions per installed app | 128 |
| Entries per definition | 1,000,000 |
| Metaobjects in one list reference | 1,024 |
| GraphQL Admin API rate | 100 points/s (Standard), 200 (Advanced), 1,000 (Plus), 2,000 (Commerce Components) |
| Bulk mutation operations | JSONL up to 100 MB; up to 5 running per app per shop (API 2026-01 and later); must finish in 24 hours |
Two that matter in practice. Forty fields is plenty for a well-modelled definition and a warning sign if you’re approaching it: you’re probably storing two things in one definition. And bulk mutations don’t process lines in order, so an import where entries reference other entries needs two passes regardless of tooling.
Running it day to day
Architecture decays without a process. Three habits keep it intact.
One change process for every bulk edit. Export, edit, validate, import, check the storefront. The same five steps whether it’s 8 rows or 8,000, with an export kept from before every change. Catching bad data before import lists the checks.
A one-page data dictionary. Every definition, its fields and keys, who owns it, and which theme sections read it. When someone asks for a new field, the dictionary is where you check whether it already exists under a different name.
Backups that include the schema. Entries as CSV, definitions as JSON, and the product references that point at entries. Backing up and restoring metaobjects has the full routine.
Untangling an existing store
If you’re reading this with a store that already has forty product metafields, here’s the path we use:
- Audit. List every metafield and metaobject definition, and for each one, the theme sections and apps that read it. Anything nothing reads is a candidate for removal.
- Group. Find duplicates (three “material” fields) and shared content copied into per-product metafields (care text, size tables, warranty terms).
- Define. Create metaobject definitions for the shared content, following the rules above, and reference metafields on the resources that use them. Use standard category metafields where they fit.
- Migrate the data by handle. Extract the distinct values, de-duplicate them into entries, and import them keyed on handles. Then set each product’s reference metafield. Migrating size guides walks through a full example.
- Switch the theme. Point each section at the new fields, one section at a time, checking the storefront as you go.
- Remove the old fields after one full cycle. Leave the old metafields in place until the new ones have been live through at least one round of content updates. Then delete them, and update the data dictionary.
The order matters: data first, theme second, deletion last. Every step can be checked and rolled back before the next one starts.
Where to go next
- Metafields vs metaobjects: the six questions that decide where a piece of data belongs.
- GIDs vs handles: why references break on import, and how handles fix it.
- Bulk importing metaobjects via CSV: admin, API and CSV routes compared.
- Bulk editing in Excel or Google Sheets: the round trip, and the spreadsheet traps.
- Catching bad data before import: nine checks for live stores.
- Migrating size guides and product specs: the apparel model in practice.
- Translating metaobjects: languages, markets, and keeping translations current.
- Backing up and restoring metaobjects: definitions, entries and references.
- Why we built Metaobjectify: the client work behind the tool.