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.
Anchor to RequirementsRequirements
- 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 concept | Shopify equivalent | Practical application |
|---|---|---|
| Table (built-in) | Resource | Standard objects like Product, Customer, and Order. |
| Table (custom) | Metaobject definition | A custom entity you define, for example, Manufacturer or SizeChart. |
| Column (on built-in table) | Metafield definition | A field added to a standard resource, such as a "Fabric" field on a Product. |
| Column (on custom table) | Metaobject field | A field on a metaobject, such as a "Website" field on a Manufacturer metaobject. |
| Row | Metaobject entry | A specific record of a metaobject, for example, "Nike". |
| Primary key (PK) | GID | The global ID, for example, gid://shopify/Metaobject/123. Always use this for relationships. |
| Foreign key (FK) | Reference type | A field typed as metaobject_reference or product_reference. |
| Constraints | Validations | Rules like required, min, max, or regex patterns applied to fields. |
| Migration file | shopify.app.toml | The 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 classification | Use this | Example |
|---|---|---|
| Attribute of a standard resource | Metafield definition | "Delivery Date" on an order |
| Reusable, standalone entity | Metaobject definition | "Designer Profile" shared across products |
| Relationship with extra metadata | Metaobject as join table | Ingredient 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_referenceorproduct_reference. - One-to-many relationship: Use a multiple reference type such as a
list.metaobject_referenceorlist.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_writeif merchants need to edit this data in the Shopify admin.
- Set to
access.storefront: Controls the Storefront API (Headless/Hydrogen).- Set to
public_readonly if your data is consumed by a headless storefront. Liquid themes can access your data regardless of this setting.
- Set to
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.
Anchor to The database viewThe database view
In a SQL database, you might design it like this:
- Table:
product_highlights(columns:id,title,icon,description). - Column: Add a foreign key to
products.
Anchor to The Shopify viewThe Shopify view
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.tomlAnchor 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.tomlEnable access.storefront = "public_read" only if you're building a headless storefront. Liquid themes can access your data regardless of this setting.
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
Anchor to Common patternsCommon patterns
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.tomlThen you can filter products using the GraphQL Admin API:
GraphQL
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:
| Identifier | Format | Use case |
|---|---|---|
GID | gid://shopify/Metaobject/123 | Internal references, relationships, API operations. Always unique and immutable. |
handle | eco-friendly-badge | Human-readable URLs, Liquid lookups, importing/exporting data. |
When to use each:
- Use
GIDin 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
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.