--- title: Data modeling with metafields and metaobjects description: >- Learn how to translate SQL database concepts to Shopify's metafields and metaobjects for app developers. source_url: html: >- https://shopify.dev/docs/apps/build/metaobjects/data-modeling-with-metafields-and-metaobjects md: >- https://shopify.dev/docs/apps/build/metaobjects/data-modeling-with-metafields-and-metaobjects.md --- # 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. *** ## Requirements * Familiarity with SQL and relational database concepts (tables, columns, foreign keys, constraints). * A basic understanding of [app configuration](https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration) with `shopify.app.toml`. * A basic understanding of [metafields](https://shopify.dev/docs/apps/build/metafields) and [metaobjects](https://shopify.dev/docs/apps/build/metaobjects). *** ## Mapping 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](https://shopify.dev/docs/apps/build/metaobjects/manage-metaobject-definitions) | A custom entity you define, for example, `Manufacturer` or `SizeChart`. | | Column (on built-in table) | [Metafield definition](https://shopify.dev/docs/apps/build/metafields/definitions) | A field added to a [standard resource](https://shopify.dev/docs/api/admin-graphql/latest/enums/MetafieldOwnerType), 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](https://shopify.dev/docs/apps/build/metafields/list-of-data-types#reference-types) | A field typed as `metaobject_reference` or `product_reference`. | | Constraints | [Validations](https://shopify.dev/docs/apps/build/metafields/list-of-validation-options) | Rules like `required`, `min`, `max`, or regex patterns applied to fields. | | Migration file | `shopify.app.toml` | The declarative file where you define your schema. | *** ## Designing your model Follow this workflow to translate your requirements into a Shopify schema. When planning your schema, keep [metafield limits](https://shopify.dev/docs/apps/build/metafields/metafield-limits) and [metaobject limits](https://shopify.dev/docs/apps/build/metaobjects/metaobject-limits) in mind. ### Step 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](https://shopify.dev/docs/api/admin-graphql/latest/enums/MetafieldOwnerType) | 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 | ### Step 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](https://shopify.dev/docs/apps/build/metafields/list-of-data-types#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. ### Step 3: Configure access controls You must explicitly define [who can read and write your data](https://shopify.dev/docs/apps/build/metafields#permissions). 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](https://shopify.dev/docs/api/storefront) (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. *** ## Example: Product highlights This example shows how to create reusable "Product Highlights" (like "Eco-Friendly" or "Lifetime Warranty") that can be assigned to products. ### The database view 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`. ### The Shopify view Create a metaobject for the highlight entity and a metafield on the `Product` resource to store the references. ### 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 ```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" ``` ### 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 ```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. ### How 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 ```liquid {% for highlight in product.metafields.app.active_highlights.value %}
{{ highlight.icon.value | image_url: width: 64 | image_tag }}

{{ highlight.title.value }}

{{ highlight.description.value }}

{% endfor %} ``` *** ## Common patterns These patterns address scenarios you'll likely encounter when working with metafields and metaobjects. ### Filtering (WHERE clauses) In a database, you add an index to columns you want to query. In Shopify, you must enable [capabilities](https://shopify.dev/docs/apps/build/metafields/use-metafield-capabilities). First, enable filtering on the metafield definition: ## File shopify.app.toml ```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 ```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](https://shopify.dev/docs/apps/build/metafields/query-using-metafields). ### Uniqueness 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 `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 ```liquid {% assign badge = shop.metaobjects.product_highlight.eco-friendly-badge %} ``` To enforce uniqueness on other fields, use the [`unique_values` capability](https://shopify.dev/docs/apps/build/metafields/use-metafield-capabilities#unique-values). ### Modeling 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](#example-product-highlights)). * **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. *** ## Next steps [Manage metafield definitions\ \ ](https://shopify.dev/docs/apps/build/metafields/definitions) [Learn how to create and manage metafield definitions using TOML or GraphQL.](https://shopify.dev/docs/apps/build/metafields/definitions) [Manage metaobject definitions\ \ ](https://shopify.dev/docs/apps/build/metaobjects/manage-metaobject-definitions) [Learn how to create and manage metaobject schemas using TOML or GraphQL.](https://shopify.dev/docs/apps/build/metaobjects/manage-metaobject-definitions) [Data types reference\ \ ](https://shopify.dev/docs/apps/build/metafields/list-of-data-types) [Explore all available metafield and metaobject field types.](https://shopify.dev/docs/apps/build/metafields/list-of-data-types) ***