Metafields and Dynamic Sources
Extend Shopify's content model with metafields to store custom data on products, collections, and other resources, then connect them to your theme.
Metafields extend Shopify’s content model, allowing you to store custom data on products, collections, customers, and other resources. They’re essential for rich product information and custom content.
What Are Metafields?
Metafields are custom fields attached to Shopify resources. Each metafield has:
- Namespace: Grouping identifier (e.g.,
custom,my_app) - Key: Field name (e.g.,
care_instructions) - Type: Data type (text, number, URL, reference, etc.)
- Value: The actual content
Creating Metafield Definitions
In Shopify Admin:
- Go to Settings → Custom data
- Select a resource type (Products, Collections, etc.)
- Click Add definition
Common Metafield Types
| Type | Use Case | Example |
|---|---|---|
single_line_text | Short text | Subtitle, tagline |
multi_line_text | Long text | Care instructions |
rich_text | Formatted content | Detailed descriptions |
url | External links | Size guide, specs PDF |
file_reference | Media files | Additional images, videos |
product_reference | Link to product | Complementary items |
collection_reference | Link to collection | Related category |
number_integer | Whole numbers | Stock count, rating |
number_decimal | Decimal numbers | Weight, dimensions |
boolean | True/false | Featured, on sale |
date | Calendar date | Release date |
json | Structured data | Specifications object |
color | Color value | Product color |
dimension | Measurements | Size dimensions |
weight | Weight value | Product weight |
Accessing Metafields in Liquid
Direct Access
{# Access product metafield #}{{ product.metafields.custom.care_instructions }}
{# With type: multi_line_text #}{{ product.metafields.custom.care_instructions | newline_to_br }}
{# With type: rich_text (already HTML) #}{{ product.metafields.custom.description }}Safe Access with Default
{%- assign care = product.metafields.custom.care_instructions -%}
{%- if care != blank -%} <div class="product__care"> <h3>Care Instructions</h3> {{ care | newline_to_br }} </div>{%- endif -%}File Reference Metafields
{%- assign size_guide = product.metafields.custom.size_guide.value -%}
{%- if size_guide -%} {%- if size_guide.media_type == 'image' -%} {{ size_guide | image_url: width: 800 | image_tag }} {%- else -%} <a href="{{ size_guide.url }}" target="_blank">View Size Guide</a> {%- endif -%}{%- endif -%}Product Reference Metafields
{%- assign complementary = product.metafields.custom.complementary_product.value -%}
{%- if complementary -%} <div class="complementary-product"> <h3>Goes well with</h3> <a href="{{ complementary.url }}"> {{ complementary.featured_image | image_url: width: 200 | image_tag }} <span>{{ complementary.title }}</span> <span>{{ complementary.price | money }}</span> </a> </div>{%- endif -%}List Metafields
{# product_reference with list capability #}{%- assign related = product.metafields.custom.related_products.value -%}
{%- if related.size > 0 -%} <div class="related-products"> <h3>Related Products</h3> {%- for item in related -%} <a href="{{ item.url }}">{{ item.title }}</a> {%- endfor -%} </div>{%- endif -%}Dynamic Sources in Theme Editor
Metafields can be connected directly in the theme editor using dynamic sources.
Making a Setting Dynamic-Source-Enabled
{ "type": "text", "id": "subtitle", "label": "Subtitle"}When merchants click the dynamic source icon, they can select:
- Product metafields
- Variant metafields
- Collection metafields
- Page metafields
Image Settings with Dynamic Sources
{ "type": "image_picker", "id": "additional_image", "label": "Additional image"}Merchants can connect a file_reference metafield.
URL Settings
{ "type": "url", "id": "external_link", "label": "External link"}Can be connected to a url metafield.
Building Metafield-Powered Features
Product Specifications Table
{# Assuming JSON metafield with specifications #}{%- assign specs = product.metafields.custom.specifications.value -%}
{%- if specs -%} <table class="product-specs"> <caption>Specifications</caption> <tbody> {%- for spec in specs -%} <tr> <th>{{ spec.label }}</th> <td>{{ spec.value }}</td> </tr> {%- endfor -%} </tbody> </table>{%- endif -%}Product Tabs with Metafields
{# sections/product-tabs.liquid #}
<div class="product-tabs"> <div class="product-tabs__nav" role="tablist"> <button role="tab" aria-selected="true" data-tab="description">Description</button>
{%- if product.metafields.custom.care_instructions != blank -%} <button role="tab" data-tab="care">Care</button> {%- endif -%}
{%- if product.metafields.custom.specifications != blank -%} <button role="tab" data-tab="specs">Specifications</button> {%- endif -%} </div>
<div class="product-tabs__panels"> <div role="tabpanel" id="description" class="product-tabs__panel"> {{ product.description }} </div>
{%- if product.metafields.custom.care_instructions != blank -%} <div role="tabpanel" id="care" class="product-tabs__panel" hidden> {{ product.metafields.custom.care_instructions | newline_to_br }} </div> {%- endif -%}
{%- if product.metafields.custom.specifications != blank -%} <div role="tabpanel" id="specs" class="product-tabs__panel" hidden> {# Render specs table #} </div> {%- endif -%} </div></div>Collection Metafields
{# Collection banner with metafield image #}{%- assign banner = collection.metafields.custom.banner_image.value -%}
<div class="collection-banner"> {%- if banner -%} {{ banner | image_url: width: 1600 | image_tag: loading: 'eager', class: 'collection-banner__image' }} {%- elsif collection.image -%} {{ collection.image | image_url: width: 1600 | image_tag }} {%- endif -%}
<div class="collection-banner__content"> <h1>{{ collection.title }}</h1>
{%- if collection.metafields.custom.subtitle != blank -%} <p class="collection-banner__subtitle"> {{ collection.metafields.custom.subtitle }} </p> {%- endif -%} </div></div>Customer Metafields
{%- if customer -%} {%- assign loyalty_points = customer.metafields.custom.loyalty_points -%}
{%- if loyalty_points != blank -%} <div class="loyalty-badge"> <span>Your Points: {{ loyalty_points }}</span> </div> {%- endif -%}{%- endif -%}Variant Metafields
Each variant can have its own metafields:
{%- for variant in product.variants -%} <div class="variant-info"> <h4>{{ variant.title }}</h4>
{%- if variant.metafields.custom.lead_time != blank -%} <p>Lead time: {{ variant.metafields.custom.lead_time }}</p> {%- endif -%}
{%- if variant.metafields.custom.country_of_origin != blank -%} <p>Origin: {{ variant.metafields.custom.country_of_origin }}</p> {%- endif -%} </div>{%- endfor -%}Page Metafields
Custom page content:
{# On page template #}{%- assign hero_image = page.metafields.custom.hero_image.value -%}{%- assign cta_text = page.metafields.custom.cta_text -%}{%- assign cta_link = page.metafields.custom.cta_link -%}
{%- if hero_image -%} <section class="page-hero"> {{ hero_image | image_url: width: 1400 | image_tag }}
{%- if cta_text != blank and cta_link != blank -%} <a href="{{ cta_link }}" class="button">{{ cta_text }}</a> {%- endif -%} </section>{%- endif -%}
<div class="page-content"> {{ page.content }}</div>Shop Metafields
Store-wide custom data:
{# Access shop metafields #}{%- assign store_message = shop.metafields.custom.announcement_message -%}
{%- if store_message != blank -%} <div class="announcement-bar"> {{ store_message }} </div>{%- endif -%}Metafield Filters
Format Values
{# Date metafield #}{{ product.metafields.custom.release_date | date: '%B %d, %Y' }}
{# Money metafield (if storing cents) #}{{ product.metafields.custom.wholesale_price | money }}
{# Rich text (already formatted) #}{{ product.metafields.custom.rich_description }}
{# Multi-line text #}{{ product.metafields.custom.notes | newline_to_br }}Weight and Dimension
{# Weight metafield #}{%- assign weight = product.metafields.custom.package_weight -%}{{ weight.value }} {{ weight.unit }}
{# Dimension metafield #}{%- assign dims = product.metafields.custom.dimensions -%}{{ dims.value }} {{ dims.unit }}Section Settings with Metafield Defaults
{ "name": "Product Details", "settings": [ { "type": "checkbox", "id": "show_care", "label": "Show care instructions", "default": true, "info": "Displays custom.care_instructions metafield if available" }, { "type": "checkbox", "id": "show_specs", "label": "Show specifications", "default": true, "info": "Displays custom.specifications metafield if available" } ]}Best Practices
Namespace Conventions
custom. # Default namespace for Admin-created metafieldsmy_theme. # Theme-specific metafieldsmy_app. # App-specific metafieldsAlways Check for Blank
{# Good: check before rendering #}{%- if product.metafields.custom.subtitle != blank -%} <p>{{ product.metafields.custom.subtitle }}</p>{%- endif -%}
{# Bad: may output empty elements #}<p>{{ product.metafields.custom.subtitle }}</p>Provide Fallbacks
{%- assign subtitle = product.metafields.custom.subtitle | default: product.vendor -%}<p>{{ subtitle }}</p>Practice Exercise
Create a metafield-powered product page with:
- Subtitle metafield (single line text)
- Care instructions (multi-line text)
- Size guide PDF (file reference)
- Complementary product (product reference)
- Specifications (JSON)
Test by:
- Adding metafield values to products
- Verifying conditional display
- Checking different metafield types
Key Takeaways
- Metafields extend standard content model
resource.metafields.namespace.keyaccess pattern- Type-specific handling for files, references, rich text
- Dynamic sources connect metafields in theme editor
- Always check
blankbefore rendering - Namespace conventions keep things organized
- List metafields for multiple values
What’s Next?
The next lesson covers Metaobjects for complex content modeling beyond metafields.
Finished this lesson?
Mark it complete to track your progress.
Discussion
Loading comments...