DEV Community

Cover image for Shopify Metaobjects vs Metafields: Choosing the Right Model
Lucy
Lucy

Posted on

Shopify Metaobjects vs Metafields: Choosing the Right Model

Metafields add one custom field to something that already exists in Shopify: a product, an order, a customer. Metaobjects create something that didn't exist before, with its own set of fields, that other resources can point to.

The short version:

  • Data describes one thing? Use a metafield.
  • Data is a reusable "thing" in its own right, like a size chart, an author profile, or a set of store locations? Model it as a metaobject and reference it from wherever it needs to show up.

That's the easy part. The part that actually costs teams time isn't picking a definition. It's picking wrong early, then discovering the mistake after forty products already depend on it.

What's the actual difference between a metafield and a metaobject?

A metafield is a key-value pair attached directly to an existing Shopify resource. It has a namespace, a key, a type, and a value. Its full address looks like product.metafields.custom.warranty_info.

It doesn't exist independently. Delete the product, and the metafield goes with it.

A metaobject is a standalone entity. You define its shape once, then create as many entries as you need in Content → Metaobjects. The definition covers:

  • name
  • fields
  • validation rules
  • access permissions

Nothing about a metaobject entry ties it to a single product. A "Designer Profile" metaobject can sit unattached to anything, or be linked from fifty different products through a reference field.

Shopify's own framing is the cleanest way to hold this in your head:

  • Metafields let you add extra columns to an existing table.
  • Metaobjects let you create an entirely new table.

If you're coming from a relational-database background, here's the mapping:

  • A metaobject definition maps to a custom table.
  • A metaobject field maps to a column on that table.
  • A metaobject entry maps to a row.
  • A metafield reference to a metaobject works like a foreign key.

Use a proper reference type (metaobject_reference, list.metaobject_reference) for that last one. Don't store a handle or ID in a plain text field. A plain-text pointer breaks Liquid and Storefront API resolution and can't be queried efficiently.

When should you use a metafield instead of a metaobject?

Reach for a metafield whenever the data is a genuine attribute of one resource and nobody else needs to reference it independently. Practical signals:

  • The value only makes sense in the context of the parent resource. A "care instructions" field only means something attached to a specific product.
  • You need it to participate in admin search, filtering, or Shopify Flow. Metafield definitions support admin_filterable capabilities that let you query products by metafield value directly through the GraphQL Admin API.
  • It's a simple scalar or a short list: text, number, date, boolean, money, a single file, or a short list of these.
  • You want to lean on Shopify's standard metafield definitions (ISBNs, care instructions, product ratings, and similar well-known fields) instead of inventing your own schema. Standard definitions don't count against your plan's metafield limits and interoperate across apps by default.

A warranty field, a release date on an order, an internal SKU code, a care-instructions block. All metafields. None of these needs to be looked up or reused from somewhere else in the store.

When does a metaobject beat a metafield?

Flip to a metaobject once any of these is true:

  • The content is reusable across many resources. A "Designer Profile," "Store Location," or "Size Chart" needs to show up on dozens of product pages without you retyping the bio, address, or measurements each time.
  • The record needs more than one related field and its own identity. A metaobject definition can hold up to 40 fields, each with its own type and validation. That's a scale a single metafield can't reach.
  • You want a dedicated URL. Metaobject definitions support a renderable capability that generates a public page and SEO metadata per entry. That matters for something like an author directory you want indexed on its own.
  • The relationship itself carries data. Say you're modeling a many-to-many relationship where the link needs extra fields, like an ingredient with a quantity specific to one recipe. Shopify's own data-modeling guidance recommends an intermediate metaobject acting as a join table here, the same pattern as a join table in SQL.

Here's the tell that shows up in the wild most often. A merchant starts by cramming repeatable content into a long multi_line_text_field metafield ("just paste the bios in as JSON"). Six months later, they need to:

  • edit one bio without touching a blob of unstructured text
  • filter profiles by role
  • reuse the same profile on the About page and three product pages

That's a metaobject that got built as a metafield.

How do metafields and metaobjects work together?

In production, you rarely pick one exclusively. You connect them. The standard pattern:

  1. Define the reusable entity as a metaobject (the "table").
  2. Add a metafield on the resource that needs to point to it. Type it as metaobject_reference for a one-to-one link, or list.metaobject_reference for one-to-many.
  3. Read the resolved data straight from the reference in Liquid. Shopify resolves the connected metaobject automatically, so you don't run a second query.

A minimal example: a reusable "Size Chart" metaobject referenced from multiple products.

# shopify.app.toml: define the reusable entity
[metaobjects.app.size_chart]
name = "Size Chart"
display_name_field = "chart_name"
access.admin = "merchant_read_write"

[metaobjects.app.size_chart.fields.chart_name]
name = "Chart Name"
type = "single_line_text_field"
required = true

[metaobjects.app.size_chart.fields.measurements]
name = "Measurements"
type = "multi_line_text_field"

# Attach the reference to Product
[product.metafields.app.size_chart_ref]
name = "Size Chart"
type = "metaobject_reference<$app:size_chart>"
access.admin = "merchant_read_write"
access.storefront = "public_read"
Enter fullscreen mode Exit fullscreen mode
{% assign chart = product.metafields.app.size_chart_ref.value %}
{% if chart %}
  <h3>{{ chart.chart_name.value }}</h3>
  <p>{{ chart.measurements.value }}</p>
{% endif %}
Enter fullscreen mode Exit fullscreen mode

Twelve products can now point at the same three size charts. Update the chart once, and every product referencing it updates in the same request. That's the behavior a plain-text metafield can't give you, because each product would be carrying its own disconnected copy.

What are the technical limits you'll actually hit at scale?

Limits rarely bite during a demo. They bite eight months in, when a catalog has grown and nobody remembers the original schema decision. Here's what's currently published:

Metafields Metaobjects
Definition cap Up to 200 definitions per resource type, per app/merchant scope 128 definitions per plan (Basic/Shopify/Advanced); 256 on Plus/Enterprise
Fields per definition N/A, one field, one value Up to 40 fields per definition
Entry/value cap Governed by resource and plan limits Up to 1,000,000 entries per definition
Reusable across resources No, bound to one resource instance Yes, referenced from any number of resources
Own URL / SEO metadata No Yes, with the renderable capability
Standard access in Liquid Always accessible Must have Storefront access explicitly enabled to appear in the Storefront API (Liquid access is unaffected)

Two mistakes account for most of the support-forum traffic on this topic:

  • Forgetting that a metaobject definition is capped at 40 fields, then trying to force an entire product-spec sheet into one definition instead of splitting it into related metaobjects.
  • Forgetting to flip on Storefront API access for a metaobject definition, then wondering why a headless build returns nothing even though the same data renders fine in Liquid.

How does the choice affect Liquid and Storefront API performance?

This is the part a lot of comparison posts skip.

Reference-typed fields resolve efficiently. Shopify fetches the connected metaobject as part of the same GraphQL response, so a metaobject_reference doesn't cost you a second round trip.

The performance risk shows up one layer deeper, with nested references: a product referencing a metaobject that itself references another metaobject. Shopify's own data-modeling guidance flags this directly. Fetching "grandchild" data through an intermediate metaobject can hit nesting limits in the Storefront API, and it's harder to query than a flat list-of-references relationship.

If you're modeling a genuine many-to-many join, expect the query cost. Cache aggressively rather than resolving nested references on every storefront request.

The practical rule: keep reference chains one level deep wherever the storefront reads them on a hot path, like a PDP or collection grid. Push anything with a second layer of nesting into a build-time or cached read instead of a live per-request resolution.

What's the decision framework for OS 2.0 architecture?

Run new custom-data requirements through these questions, in order:

  1. Does this value only ever apply to one resource instance? → Metafield.
  2. Does it need to be edited, searched, or reused in more than one place? → Metaobject.
  3. Does it need more than a handful of related fields, or its own indexed page? → Metaobject.
  4. Is it a relationship between two things that itself carries data, like a quantity, a sort order, or a date range? → Intermediate metaobject acting as a join table.
  5. Does it need to power admin filtering or a Shopify Flow trigger? → Metafield with the relevant capability enabled, even if the value also happens to reference a metaobject.

None of these are permanent decisions written in stone. Shopify allows changing some field-level settings after the fact. But changing a field's underlying type is tightly restricted once entries exist. The decision is far cheaper to get right at schema design time than to fix once a theme and forty products depend on it.

Migration: what happens if you chose wrong?

The most common wrong turn is starting with a large multi_line_text_field or JSON-in-a-text-field metafield for something that should have been a metaobject from day one.

The fix is mechanical but not instant:

  • Create the metaobject definition with the correct fields.
  • Backfill entries from the existing metafield values using the GraphQL Admin API. This is a scripted, one-time migration, not something to do by hand past a handful of records.
  • Add a new metaobject_reference metafield on the parent resource(s) and point it at the new entries.
  • Update theme sections to read from the reference instead of the flat text field.
  • Leave the old metafield in place until the new path is verified in production, then remove it.

The inverse mistake, over-modeling a single-use attribute as a metaobject, is cheaper to unwind. Fold the fields back into a metafield and delete the now-unused definition.

It's the direction most teams don't expect to need. That's exactly why it's worth checking before building: not every reusable-sounding field is actually reused anywhere yet.

FAQ

Can a metaobject reference another metaobject?
Yes. Reference fields can point from one metaobject to another, which is how join-table patterns work. Keep an eye on nesting depth. Storefront API queries that resolve several reference layers deep get more expensive and can hit query-cost limits.

Do metaobjects work in Liquid the same way as the Storefront API?
Liquid can read metaobject data regardless of the Storefront API access setting. Headless storefronts built on the Storefront API need access.storefront = "public_read" explicitly set on the definition, or the query returns nothing.

Is there a hard cap on how many metaobjects I can create?
Definitions are capped by plan: 128 on standard plans, 256 on Plus/Enterprise. Each definition can hold up to 1,000,000 entries. Standard metaobject definitions Shopify ships for things like product taxonomy don't count against that cap.

The default that scales

Most OS 2.0 builds settle into the same pattern once the catalog is big enough to notice:

  • metafields for the attributes that belong to one product, order, or customer
  • metaobjects for the content that has to look the same everywhere it appears
  • reference fields doing the connecting work in between

Getting that split right before the theme is built around it is the difference between a schema change and a rebuild. It's also the baseline architecture Lucent Innovation's Shopify Development team designs into every custom OS 2.0 storefront, so merchant content teams aren't stuck re-modeling data six months after launch.

What's the messiest metafield-vs-metaobject call you've had to untangle on a live store?

Top comments (0)