Part of our guide to Shopify Metafields: Display Them in Your Storefront.
Once a metafield is showing on the storefront, the next question is how to render each type correctly — rich text as HTML, a list as a loop, a date formatted, a file as a link — and how to let a merchant pick which metafield a section shows without editing code. This guide is the reference for both: the Liquid filters per type, and the token pattern that turns a hard-coded key into an editor setting.
If you have not yet rendered a metafield at all, start with Display metafields in a section, then use this page for the type-by-type detail. The concepts behind namespaces and types are in the Shopify Metafields pillar.
The two core filters: metafield_tag and metafield_text
Shopify gives you two filters built specifically for metafields:
metafield_tag— renders the metafield as an HTML element appropriate to its type. A rich-text field becomes its formatted HTML; aurlbecomes an<a>; a file image becomes an<img>. Use it when you want Shopify's default markup.metafield_text— renders the metafield's value as plain text, stripping markup. Use it when you need the raw value inside your own element, or for a meta tag / attribute where HTML would be wrong.
{% comment %} Rich text, rendered as HTML: {% endcomment %}
{{ product.metafields.custom.care_guide | metafield_tag }}
{% comment %} Same field, flattened to plain text: {% endcomment %}
<meta name="description" content="{{ product.metafields.custom.care_guide | metafield_text }}">
For plain single-line text you can also just output {{ product.metafields.custom.material }}
directly — the metafield object renders its value. The filters matter most for the
richer types below.
Reaching the underlying value with .value
Every typed metafield exposes a .value that gives you the native data — a
number as a number, a boolean as true/false, a file as a file object, a reference
as the referenced resource. Reach for .value when you need to compare, format,
or drill into the data rather than just print it:
{% assign warranty = product.metafields.custom.warranty_years.value %}
{% if warranty > 1 %}
<span class="badge">{{ warranty }}-year warranty</span>
{% endif %}
Type-by-type rendering
Number (integer / decimal)
Numbers come back as numbers, so you can do math and formatting on them:
{% assign weight = product.metafields.custom.weight_g.value %}
{% if weight != blank %}
<p>Weight: {{ weight }} g ({{ weight | divided_by: 1000.0 }} kg)</p>
{% endif %}
Boolean
A boolean metafield's .value is true or false, so test it directly:
{% if product.metafields.custom.handmade.value %}
<span class="badge">Handmade</span>
{% endif %}
Date and date-time
Date metafields hold an ISO value; format with the date filter:
{% assign released = product.metafields.custom.release_date.value %}
{% if released != blank %}
<p>Released {{ released | date: "%B %e, %Y" }}</p>
{% endif %}
URL
Output the value as the href, and always give the link readable text:
{% if product.metafields.custom.guide_url != blank %}
<a href="{{ product.metafields.custom.guide_url }}">Read the full guide</a>
{% endif %}
File and image references
A file_reference gives a file object via .value. For an image, pass it through
image_url and image_tag; for any file, use its url:
{% assign sheet = product.metafields.custom.spec_sheet.value %}
{% if sheet != blank %}
<a href="{{ sheet.url }}" download>Download spec sheet</a>
{% endif %}
{% assign badge = product.metafields.custom.badge_image.value %}
{% if badge != blank %}
{{ badge | image_url: width: 120 | image_tag: alt: badge.alt }}
{% endif %}
List metafields
A list.* type holds multiple values. Its .value is an array, so loop over it:
{% assign materials = product.metafields.custom.materials.value %}
{% if materials.size > 0 %}
<ul class="materials">
{% for material in materials %}
<li>{{ material }}</li>
{% endfor %}
</ul>
{% endif %}
For a list of file or product references, each item in the loop is itself an
object — read item.url, item.title, and so on, exactly as you would a single
reference.
Metaobject references
If a metafield points at a metaobject, .value gives you the metaobject, and
you read its fields by key:
{% assign designer = product.metafields.custom.designer.value %}
{% if designer != blank %}
<p>Designed by {{ designer.name.value }} — {{ designer.country.value }}</p>
{% endif %}
Making the metafield merchant-configurable (the token pattern)
Hard-coding product.metafields.custom.material works, but it means editing Liquid
every time you want a section to show a different field. You can instead let a
merchant type the namespace and key into the theme editor, so one section
renders any metafield they choose. Add two settings to the schema:
{% schema %}
{
"name": "Metafield value",
"tag": "section",
"settings": [
{
"type": "text",
"id": "mf_namespace",
"label": "Metafield namespace",
"default": "custom"
},
{
"type": "text",
"id": "mf_key",
"label": "Metafield key",
"info": "Example: material"
}
],
"presets": [{ "name": "Metafield value" }]
}
{% endschema %}
Then resolve the field dynamically with bracket lookup, since the namespace and key are now variables rather than literals:
{%- assign ns = section.settings.mf_namespace -%}
{%- assign key = section.settings.mf_key -%}
{%- assign field = product.metafields[ns][key] -%}
{% if field != blank %}
<div class="metafield-value">
{{ field | metafield_tag }}
</div>
{% endif %}
Now a merchant points the section at any metafield from the editor — custom.material,
specs.dimensions, custom.care_guide — with no code change per field, and the
metafield_tag filter renders each type appropriately. This is the pattern that
lets a single section serve a whole catalog of different data. It is also, in
essence, how SectionGuard exposes metafields as tokens: you pick the
field in the editor, and the section renders it — while running from the app rather
than a theme file, so a theme update never removes the block.
A note on the reliability of these filters
metafield_tag and metafield_text are Shopify's current, supported filters for
rendering metafields — they replaced older, less predictable approaches to
outputting rich content. Prefer them over manually reconstructing HTML from a
metafield's raw value: you get output that matches the metafield's type, and it
keeps working as Shopify evolves the underlying types. For the end-to-end build
that puts these into a real section, see
Display metafields in a section,
and to compare rendering from a theme file versus from an app, read the
honest comparison.