Your theme is hiding data.
Every product, page, and collection in your store can carry custom fields your theme never shows. That data lives in metafields, and most merchants never see it rendered because nobody wrote the Liquid to pull it out.

What Are Shopify Metafields?
Shopify metafields are custom key-value fields that attach extra data to a product, page, collection, order, or other store resource, beyond what Shopify's built-in fields cover. Each metafield has a namespace, a key, a type, and a value, and Shopify's own documentation lists 20+ supported types including text, product references, files, and JSON.
Shopify's default fields cover the basics: title, price, description, images. But merchants constantly need more. A warranty length. A care instruction sheet. A linked "frequently bought with" product. A care-and-fit chart stored as structured JSON.
Metafields fill that gap. You define one in your Shopify admin (or through an app), give it a namespace and key like custom.warranty_months, pick a type, and populate a value. From that point, the data exists on the resource and Liquid can read it.
Shopify's own documentation describes the identifier as "a combination of namespace and key," with namespaces acting as containers that prevent naming collisions between your custom fields and anyone else's. That matters once you install a theme or app that also uses metafields: a shared namespace avoids a silent data clash.
Defining a metafield is step one. Populating it is step two. Skip step two and your Liquid will render nothing, no error, just a blank space where your data should be.
How Do You Access a Metafield in Liquid?
You access a metafield in Liquid through the pattern
{{ object.metafields.namespace.key }}, whereobjectis the resource the metafield is attached to (product,page,collection, and so on). Appending.valuereturns the actual data; omitting it returns a GID reference string for every type except plain text.
The access path always starts with the host object. A metafield on a product lives at product.metafields.<namespace>.<key>. The same metafield type on a page would be page.metafields.<namespace>.<key>. Only the object changes; the rest of the pattern holds.
You can find this exact path printed directly under the field name on the metafield definition screen in your Shopify admin. Copy it instead of guessing at the namespace.
{{ product.metafields.custom.warranty_months }}
{{ product.metafields.custom.warranty_months.value }}
For plain text, both lines above print the same thing. For every other type (images, products, lists, JSON), the first line prints a GID (a backend reference like gid://shopify/Metafield/123456789) and the second line prints the actual data. That single difference causes most of the "why is my metafield broken" questions developers run into, and it's the first thing to check when output looks wrong.

How Do You Display a Single-Line Text Metafield?
A single-line or multi-line text metafield outputs directly with
{{ product.metafields.namespace.key.value }}. Text is the one metafield type where Liquid returns a usable string whether or not you include.value, though using.valueconsistently avoids confusion later.
Text is the simplest case and the right place to start if you're new to metafields.
{{ product.metafields.custom.care_instructions.value }}
If the metafield is empty, this prints nothing. If it's populated, you get the stored string, ready to drop into a <p> tag, a tooltip, or a product-page accordion.
Because text behaves identically with or without .value, it's easy to assume every metafield type works this way. It doesn't. Everything past plain text needs either a filter, a loop, or an attribute chain, which is where most of the real work in this guide starts.
How Do You Loop Through a List Metafield?
A list metafield (text list, image list, product list) must be looped with
{% for %}. Without a loop,.valueprints every item run together with no separators. Aforloop.lastcheck adds commas between items without a trailing comma.
List metafields store multiple values under one key, like a list of care symbols or a list of related SKUs. The value comes back as an array, not a single string, so a loop is required to render it cleanly.
{% for item in product.metafields.custom.care_symbols.value %}
{{ item }}{% unless forloop.last %}, {% endunless %}
{% endfor %}
Drop the loop and you'll see the array dumped as one unreadable block of text. Drop the forloop.last check and you'll see every item correctly separated except the last one, which trails a stray comma. Both are easy to miss in a quick preview and obvious the moment a customer looks closely.
How Do You Display an Image or File Metafield?
An image or file metafield's
.valuereturns a URL string, not a ready image. Pass it through theimage_urlfilter for a clean CDN link, orimage_tagfor a complete, ready-to-render<img>element. A media list metafield needs both a loop and one of these filters applied inside it.
Single media metafield:
{{ product.metafields.custom.size_chart_image.value | image_url: width: 800 }}
{{ product.metafields.custom.size_chart_image.value | image_tag }}
image_url gives you the raw CDN string, useful for a CSS background or a custom <img src> you're building yourself. image_tag gives you the finished element: alt text, loading attributes, and all.
Media list metafield, looped:
{% for file in product.metafields.custom.spec_sheets.value %}
{{ file | image_tag }}
{% endfor %}
Skip the filter on a media list and Liquid loops through drop objects instead of images, printing values that mean nothing on the page. Shopify's Liquid object reference confirms the underlying mechanic: a file_reference metafield resolves to a generic_file object, and a media-type reference resolves to a media object, neither of which is a displayable image until you filter it.

How Do You Display a Product or Collection Reference Metafield?
A product or collection reference metafield's
.valuereturns the full product or collection object, not just an ID. Access any attribute directly,.value.title, or assign it to a variable first for cleaner, more readable code when you need several attributes.
This is where metafields start doing real work: linking resources to each other without an app.
{# Direct access #}
{{ product.metafields.custom.replacement_part.value.title }}
{# Assigned: cleaner for multiple attributes #}
{% assign related = product.metafields.custom.replacement_part.value %}
<a href="{{ related.url }}">{{ related.title }}</a>
<span>{{ related.price | money }}</span>
A product-list reference follows the same logic with a loop added:
{% assign bundle_items = product.metafields.custom.bundle_contents.value %}
{% for item in bundle_items %}
<li>{{ item.title }} ({{ item.price | money }})</li>
{% endfor %}
Building a feature like this from scratch typically takes a few hours of careful Liquid work to get the edge cases right. If you'd rather skip straight to a tested result, LiquidBoost's marketplace has drop-in snippets for product bundles, related-item displays, and other metafield-powered sections. One-time purchase, no monthly app fee.

How Do You Access JSON and Metaobject Metafields?
A JSON metafield's
.valuereturns a Liquid hash you read by chaining the property name:.value.warranty_years. A metaobject metafield works identically, chaining whatever fields you defined on the metaobject. Both let content editors or developers store structured, multi-field data under a single metafield key.
JSON metafields hold structured data without needing a separate metaobject definition:
{{ product.metafields.custom.specs.value.weight_kg }}
{{ product.metafields.custom.specs.value.material }}
Metaobjects work the same way once the reference resolves to the metaobject's fields:
{{ product.metafields.custom.manufacturer.value.name }}
{{ product.metafields.custom.manufacturer.value.country }}
Choosing between JSON and a metaobject comes down to who edits the data. A metaobject gives content editors a friendly admin form for each field. JSON is faster to set up and fine when only you or your app touches the value. Both render in Liquid almost identically, so the decision is an editing-experience call, not a technical one.
Why Is Your Metafield Showing a GID or Blank Value Instead of Data?
A metafield printing a GID string means
.valuewas omitted. A metafield printing nothing means it was defined but never populated, or it's being read from the wrong host object (checkingpage.metafieldsfor a metafield actually attached toproduct, for example).
Three mistakes account for nearly every broken metafield:
Missing .value. This is the single most common cause of a gid://shopify/... string showing up where data should be. Add .value and it resolves.
Defined but not populated. Creating a metafield definition in the admin does not fill in a value. Each product (or page, or collection) needs its own value entered separately. An unpopulated metafield renders as nothing, with no error to flag the problem.
Wrong host object. A metafield attached to product will not appear under page.metafields, even with an identical namespace and key. Check which resource the metafield was actually defined on.
| Symptom | Likely Cause | Fix |
|---|---|---|
Prints gid://shopify/... |
Missing .value |
Add .value |
| List prints with no separators | Looping without a comma check | Add forloop.last check |
| Image won't display | .value URL used directly |
Apply image_url or image_tag |
| Prints nothing at all | Metafield not populated | Add a value in the admin |
| Prints nothing at all | Wrong host object | Confirm which resource owns the metafield |
Source: access patterns confirmed against Shopify's Liquid metafield object documentation.
Frequently Asked Questions
How do I access a metafield value in Shopify Liquid?
Use the pattern {{ object.metafields.namespace.key.value }}, replacing object with the resource type (product, page, collection). Omitting .value returns a GID reference instead of the actual data for every metafield type except plain text.
Why does my Shopify metafield print a GID instead of my data?
A GID like gid://shopify/Metafield/123456789 appears when .value is left off the access path. Shopify stores an internal reference by default; appending .value resolves it to the human-readable data for display.
Can I create a metafield directly in Liquid?
No. Metafields must be created through the Shopify admin, through an app, or via the GraphQL Admin API. Liquid can only read and display metafield values that already exist; it has no write access to create or populate them.
What's the difference between a metafield and a metaobject in Shopify?
A metafield adds one custom field to an existing resource like a product or page. A metaobject is a standalone, multi-field object you define yourself, then link to resources by reference. Use metafields for single custom fields and metaobjects when you need several related fields bundled together.
Ready to skip the custom code?
Metafields store the data. Liquid still has to render it, and rendering it well (loops, filters, fallback states) takes real development time. A tested, pre-built snippet gets you the finished feature without writing or debugging that Liquid yourself.
Metafields get you most of the way to a richer product page. Pairing that data with a tested Liquid snippet gets you the rest of the way, without the hours of trial and error.
Browse LiquidBoost's snippet marketplace →
One-time purchase. No monthly fees. Install in minutes.