Skip to main content

Data modeling with metafields and metaobjects

In this guide, you'll learn how to design Shopify-native data models using metafields and metaobjects. If you're familiar with relational databases, you'll learn how to translate concepts like tables, columns, foreign keys, and constraints into Shopify's architecture.


  • Familiarity with SQL and relational database concepts (tables, columns, foreign keys, constraints).
  • A basic understanding of app configuration with shopify.app.toml.
  • A basic understanding of metafields and metaobjects.

Anchor to Mapping SQL conceptsMapping SQL concepts

When building an app, you often need to store data that doesn't fit into standard Shopify resources like products or orders.

In a relational database, you'd usually solve this by updating your database structure:

  • You add a new column to an existing table to track new information (for example, an "estimated delivery date" on an orders table).
  • You create brand-new tables to store new types of data (like a separate vendors table).

In Shopify, you achieve this using metafields and metaobjects:

  • Metafields let you add extra fields (think: new columns) to existing Shopify resources, like products, orders, or customers.
  • Metaobjects let you define totally new kinds of data (like custom tables), which you can relate to other resources or use in new ways.

Use the following table to map your database knowledge to Shopify concepts:

Relational database conceptShopify equivalentPractical application
Table (built-in)ResourceStandard objects like Product, Customer, and Order.
Table (custom)Metaobject definitionA custom entity you define, for example, Manufacturer or SizeChart.
Column (on built-in table)Metafield definitionA field added to a standard resource, such as a "Fabric" field on a Product.
Column (on custom table)Metaobject fieldA field on a metaobject, such as a "Website" field on a Manufacturer metaobject.
RowMetaobject entryA specific record of a metaobject, for example, "Nike".
Primary key (PK)GIDThe global ID, for example, gid://shopify/Metaobject/123. Always use this for relationships.
Foreign key (FK)Reference typeA field typed as metaobject_reference or product_reference.
ConstraintsValidationsRules like required, min, max, or regex patterns applied to fields.
Migration fileshopify.app.tomlThe declarative file where you define your schema.

Anchor to Designing your modelDesigning your model

Follow this workflow to translate your requirements into a Shopify schema. When planning your schema, keep metafield limits and metaobject limits in mind.

Anchor to Step 1: Classify your dataStep 1: Classify your data

Decide where your data belongs based on its relationship to the core commerce model:

Data classificationUse thisExample
Attribute of a standard resourceMetafield definition"Delivery Date" on an order
Reusable, standalone entityMetaobject definition"Designer Profile" shared across products
Relationship with extra metadataMetaobject as join tableIngredient list with quantities per recipe

Anchor to Step 2: Define relationships using typesStep 2: Define relationships using types

In a relational database, you might store an ID as an integer or string. In Shopify, you must use reference types:

  • One-to-one relationship: Use a single reference type such as a metaobject_reference or product_reference.
  • One-to-many relationship: Use a multiple reference type such as a list.metaobject_reference or list.product_reference.

Don't store handles or IDs in plain text fields (single_line_text_field) to create relationships. This breaks the connection between related data and prevents Shopify from retrieving it efficiently in Liquid or the Storefront API.

Anchor to Step 3: Configure access controlsStep 3: Configure access controls

You must explicitly define who can read and write your data. This is similar to defining database user permissions or row-level security.

  • access.admin: Controls the Shopify admin and GraphQL Admin API.
    • Set to merchant_read_write if merchants need to edit this data in the Shopify admin.
  • access.storefront: Controls the Storefront API (Headless/Hydrogen).
    • Set to public_read only if your data is consumed by a headless storefront. Liquid themes can access your data regardless of this setting.

Anchor to Example: Product highlightsExample: Product highlights

This example shows how to create reusable "Product Highlights" (like "Eco-Friendly" or "Lifetime Warranty") that can be assigned to products.

In a SQL database, you might design it like this:

  1. Table: product_highlights (columns: id, title, icon, description).
  2. Column: Add a foreign key to products.

Create a metaobject for the highlight entity and a metafield on the Product resource to store the references.

Anchor to 1. Define the table (metaobject)1. Define the table (metaobject)

Add the following to your shopify.app.toml (located in your app's project root). When you deploy your app with shopify app deploy, Shopify creates these definitions on the store:

File

shopify.app.toml
[metaobjects.app.product_highlight]
name = "Product Highlight"
display_name_field = "title"
description = "A reusable badge or highlight for product pages"

# Allow merchants to edit these in the Shopify admin
access.admin = "merchant_read_write"

# Fields (Columns)
[metaobjects.app.product_highlight.fields.title]
name = "Title"
type = "single_line_text_field"
required = true
validations.max = 50

[metaobjects.app.product_highlight.fields.icon]
name = "Icon"
type = "file_reference" # Strongly typed file storage

[metaobjects.app.product_highlight.fields.description]
name = "Description"
type = "multi_line_text_field"

Anchor to 2. Define the foreign key (metafield)2. Define the foreign key (metafield)

Add the following to shopify.app.toml. This adds a "column" to the built-in Product table that points to our custom table:

File

shopify.app.toml
[product.metafields.app.active_highlights]
name = "Active Highlights"
description = "Select the highlights to display on this product"

# This is a One-to-Many relationship (List of FKs)
type = "list.metaobject_reference<$app:product_highlight>"

# Merchants select the highlights on the product page
access.admin = "merchant_read_write"

# Expose to Headless channels (Liquid works automatically)
access.storefront = "public_read"
Note

Enable access.storefront = "public_read" only if you're building a headless storefront. Liquid themes can access your data regardless of this setting.

Anchor to How to use this dataHow to use this data

Using specific data types unlocks built-in functionality. For example, the file_reference type gives you access to Liquid's image filters, and metaobject_reference automatically resolves related objects without extra queries.

Liquid (theme) access:

Liquid

{% for highlight in product.metafields.app.active_highlights.value %}
<div class="highlight">
{{ highlight.icon.value | image_url: width: 64 | image_tag }}
<h3>{{ highlight.title.value }}</h3>
<p>{{ highlight.description.value }}</p>
</div>
{% endfor %}

These patterns address scenarios you'll likely encounter when working with metafields and metaobjects.

Anchor to Filtering (WHERE clauses)Filtering (WHERE clauses)

In a database, you add an index to columns you want to query. In Shopify, you must enable capabilities.

First, enable filtering on the metafield definition:

File

shopify.app.toml
[product.metafields.app.color]
name = "Color"
type = "single_line_text_field"
# Enables filtering in API queries
capabilities.admin_filterable = true

Then you can filter products using the GraphQL Admin API:

GraphQL

query ProductsByColor {
products(first: 10, query: "metafields.$app.color:\"blue\"") {
edges {
node {
id
title
}
}
}
}

For more filtering patterns including numeric ranges and multiple conditions, refer to Query using metafields.

Anchor to Uniqueness and handlesUniqueness and handles

Shopify resources and metaobjects have two identifiers:

IdentifierFormatUse case
GIDgid://shopify/Metaobject/123Internal references, relationships, API operations. Always unique and immutable.
handleeco-friendly-badgeHuman-readable URLs, Liquid lookups, importing/exporting data.

When to use each:

  • Use GID in your code for relationships and API calls. Reference types (metaobject_reference, product_reference) store GIDs automatically.
  • Use handles when you need stable, readable identifiers for URLs or cross-store data migration.

Handles are auto-generated from the display_name_field but can be customized. To look up a metaobject by handle in Liquid:

Liquid

{% assign badge = shop.metaobjects.product_highlight.eco-friendly-badge %}

To enforce uniqueness on other fields, use the unique_values capability.

Anchor to Modeling many-to-many relationshipsModeling many-to-many relationships

In SQL, many-to-many relationships often require a join table. In Shopify, you have two options:

Option A: List of references (recommended)

Store a list of references on the parent object (as shown in the Product highlights example).

  • Best for: Simple relationships where you just need to link object A to object B.
  • Pros: Easy to query in Liquid and Storefront API, and simpler admin UI.

Option B: The intermediate metaobject

Create a third metaobject that has two reference fields (product_reference and highlight_reference) plus extra fields, for example, sort_order.

  • Best for: When the relationship itself has data, for example, "Quantity" in a recipe ingredient list.
  • Cons: Complex to query. Fetching the "grandchild" data in Storefront API can hit nesting limits.


Was this page helpful?