Guide

The Ultimate Guide to Structuring Custom Data Architectures on Shopify

How to structure Shopify custom data that still makes sense two years from now: the building blocks, five design rules, relationship patterns, three reference architectures and a way out of an existing mess.

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 as app in 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:

  1. 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.
  2. Group. Find duplicates (three “material” fields) and shared content copied into per-product metafields (care text, size tables, warranty terms).
  3. 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.
  4. 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.
  5. Switch the theme. Point each section at the new fields, one section at a time, checking the storefront as you go.
  6. 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

Stop typing metaobjects in one at a time.

Export a definition to see the exact columns, fill it in your spreadsheet, and every row is checked before anything touches your store.