Part of our guide to Shopify Metafields: Display Them in Your Storefront.
This guide takes a metafield from "defined in the admin" to "showing on the storefront" inside a custom section. You will build a Product details section that renders a product's material, a care guide, and a downloadable spec sheet — each read from its own metafield, each hidden when the product has no value for it. It assumes an Online Store 2.0 theme and that you can edit theme code.
If you are still deciding whether a metafield is the right home for this data at all, start with the Shopify Metafields pillar; it covers types, namespaces, and when a metafield beats a plain theme setting.
Step 1 — Define the metafields you will read
You cannot render a metafield the theme cannot see. In the admin go to Settings → Custom data → Products → Add definition and create three definitions, making sure each has storefront access enabled:
custom.material— type Single line textcustom.care_guide— type Rich textcustom.spec_sheet— type File
Then open a product and fill those fields in under Metafields at the bottom of its page. A section can only render what a product actually has, so give at least one product real values to test against.
Step 2 — Create the section file
Add a section file at sections/product-details.liquid. Because this section
reads from product, it belongs on the product template — add it there in the
theme editor once the file exists.
Start with the wrapper and the simplest field, the single-line text material:
<section class="product-details">
<div class="product-details__inner">
{% if section.settings.heading != blank %}
<h2 class="product-details__heading">{{ section.settings.heading }}</h2>
{% endif %}
{% if product.metafields.custom.material != blank %}
<p class="product-details__row">
<span class="product-details__label">Material:</span>
{{ product.metafields.custom.material }}
</p>
{% endif %}
</div>
</section>
The != blank guard is doing real work here. Metafields are optional per product,
so without the guard, products with no material would render a stray "Material:"
label with nothing after it.
Step 3 — Render a rich-text metafield
A rich_text_field does not output as plain text — it holds structured rich
text. Piping it through the metafield_tag filter renders it as proper HTML
(paragraphs, links, lists) instead of escaped markup:
{% if product.metafields.custom.care_guide != blank %}
<div class="product-details__care">
<h3>Care</h3>
{{ product.metafields.custom.care_guide | metafield_tag }}
</div>
{% endif %}
If you output a rich-text metafield with just {{ … }}, older behaviour renders
the value but you lose control over the wrapping markup; metafield_tag is the
current, predictable way to render it. The full list of which filter suits which
type is in
Metafield tokens and filters.
Step 4 — Render a file metafield
A file_reference metafield gives you a file object, not a URL string. To
build a download link you read the file's url and, for a nicer label, its
filename:
{% if product.metafields.custom.spec_sheet != blank %}
{% assign spec = product.metafields.custom.spec_sheet.value %}
<p class="product-details__download">
<a href="{{ spec.url }}" download>
Download spec sheet
</a>
</p>
{% endif %}
Note the .value — for typed metafields, .value gives you the underlying object
(here, the file) so you can reach its properties. If the file is an image you can
also pass it through image filters like image_url and image_tag.
Step 5 — Add the schema
Give the section a name, one editable heading, and a preset so it appears in the editor's "Add section" menu. Add this at the bottom of the file:
{% schema %}
{
"name": "Product details",
"tag": "section",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Product details"
}
],
"presets": [
{ "name": "Product details" }
]
}
{% endschema %}
The metafield keys are hard-coded in the Liquid here, which is fine for a fixed layout. If you want a merchant to choose which metafield the section shows from the editor — without editing code for each new field — expose it as a token; that pattern is covered in Metafield tokens and filters.
Step 6 — Add the section and verify
Save the file. In Online Store → Themes → Customize, open a product template, click Add section, and add Product details. Preview a product that has the metafields filled in — you should see the material, the care guide, and the download link — and a product that does not, where the whole block (or each empty row) simply does not render.
Metafields on other resources
The same pattern works beyond products. Swap the object in front of .metafields:
- Collection:
collection.metafields.custom.introin a collection section. - Page:
page.metafields.custom.disclaimerin a page template. - Shop-wide:
shop.metafields.custom.hoursfor a value that is the same store-wide.
The type-to-filter rules are identical regardless of the resource — only the object in front changes.
Keeping this section alive across theme updates
This section now lives in sections/product-details.liquid — a theme file. The
metafield data is safe (it lives on the products), but the file that renders it
is part of this theme version. Install a new version of the theme, or switch
themes, and Shopify publishes a fresh copy of the theme's files; your
product-details.liquid is not carried over, and the block disappears even though
the data behind it is untouched.
Two ways to protect it: keep a disciplined re-apply workflow after every theme update (the method is here), or render the section from an app via a Theme App Extension so no update or swap can touch it. If this is a section you rely on, SectionGuard renders metafield-driven blocks like this from the app — reading your existing metafields, surviving every theme change.