Shopify Metafield Liquid Filters and Tokens

The Liquid filters for each metafield type — metafield_tag, metafield_text, list handling, date and money formatting — plus how to make a section's metafield merchant-configurable.

5 min readUpdated

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; a url becomes 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.