shopatch
← Blog

Per-product content in Shopify: metafields and metaobjects instead of tag-if chains

Short answer: If you show different text or images per product (a series, a motif, a material, a brand), a {% if product.tags contains %} chain is the wrong tool past two or three cases. It hardcodes the content in the template, so every new case is another code edit. Store the content as data instead: a metafield when it is unique to each product, a metaobject when the same content is shared across many products. Then a new case is a field you fill in, not code you rewrite.

This is one of the most common questions on the Shopify forums, in a dozen shapes: how do I show a specific section on the product page depending on the product? The first answer everyone reaches for (we did too) is a tag-if chain. It works, so it spreads, and then it quietly becomes the thing nobody wants to touch. At Shopatch we build it the other way round, so here is the same goal three ways, from quick-and-fragile to the version that scales to hundreds of products without touching code again.

Why does a tag-if chain stop scaling?

A tag-if chain stops scaling because it puts content inside logic: each product type or motif becomes another branch in the template, and the branches multiply. You tag products and switch on the tag:

{% if product.tags contains 'unicorn' %}
  <h2>The Unicorn series</h2>
  <p>Hand-drawn, small-batch, a bit of sparkle.</p>
{% elsif product.tags contains 'beetle' %}
  <h2>The Beetle series</h2>
  <p>Matte finish, built to last.</p>
{% endif %}

This is fine for two or three fixed cases that never change. Beyond that it costs you on every edit: a new motif means new code, the content lives in a file your team cannot safely touch, and once you also branch on product type or colour the combinations grow until nobody can read them. The moment you paste a fourth elsif, switch approaches.

How do you move per-product content into metafields?

You move per-product content into metafields by defining the fields once under Settings, Custom data, Products, then reading them in the template. Create, for example, a single-line text “Motif heading”, a rich-text “Motif text” and an image “Motif image”. Every product now carries those fields on its admin page, and the template prints whatever each product holds:

{% assign heading = product.metafields.custom.motif_heading %}
{% if heading != blank %}
  <section class="motif">
    <h2>{{ heading }}</h2>
    {{ product.metafields.custom.motif_text | metafield_tag }}
    {% if product.metafields.custom.motif_image != blank %}
      {{ product.metafields.custom.motif_image.value | image_url: width: 1200
         | image_tag: loading: 'lazy', class: 'motif__image' }}
    {% endif %}
  </section>
{% endif %}

The content is now out of the code and editable by anyone. The limit: it lives per product. If fifty products belong to the same “Unicorn series”, you are typing that blurb fifty times, and re-editing fifty products when it changes. Metafields are the right tool when the content is genuinely unique to each product, and the wrong one when it is shared.

How do you share the same content across many products?

You share content across many products with a metaobject: you define the content once as its own entry, then point each product at it. Under Settings, Custom data, Metaobjects, create a definition called “Motif” with fields heading (text), description (rich text) and image (file), and add one entry per motif. Then add a single product metafield of type “Metaobject reference” pointing at Motif, so each product just picks its motif from a dropdown. The template follows the reference:

{%- assign motif = product.metafields.custom.motif.value -%}
{%- if motif -%}
  <section class="motif">
    {%- if motif.image.value != blank -%}
      {{ motif.image.value | image_url: width: 1200
         | image_tag: loading: 'lazy', class: 'motif__image' }}
    {%- endif -%}
    <h2>{{ motif.heading.value }}</h2>
    {{ motif.description | metafield_tag }}
  </section>
{%- endif -%}

Change the Unicorn description once and every unicorn product updates. A new motif is one metaobject entry plus a dropdown pick on each product, still zero code. If a product can belong to several motifs, make the metafield a list of metaobject references and loop over product.metafields.custom.motifs.value.

Why is my metafield showing raw code or JSON?

A metafield shows raw JSON when it holds rich text and you output its .value directly, because a rich-text field is stored as a JSON structure, not as HTML. The fix is the metafield_tag filter, which renders the field as proper markup:

{{ product.metafields.custom.motif_text | metafield_tag }}
{{ motif.description | metafield_tag }}

Two related gotchas clear up the rest of the confusion. Metaobject fields are read with .value on the field itself (motif.heading.value), whereas a metaobject-reference metafield gives you the object with .value on the metafield (product.metafields.custom.motif.value). And an image stored in a File field needs .value before the image_url filter, so product.metafields.custom.motif_image.value | image_url.

Metafields or metaobjects: which should you use?

Use a metafield for content unique to one product, and a metaobject for content shared across many. The quick test is ownership: if the words and image belong to this one product, a metafield keeps them where they are edited. If the same block appears on every product in a series, brand or material, a metaobject stores it once and every product references it, so you maintain it in a single place. Reach for the tag-if chain only when there are two or three cases that will genuinely never change; below that threshold, data is overkill.

Do metafields and metaobjects survive a theme update?

Yes, metafields and metaobjects survive a theme update, because they live in your store’s data, not in the theme files. Updating a theme installs fresh theme files, but your custom data is untouched, so the content stays exactly where it was. The only theme-side piece is the small snippet that displays it, which you keep in your own section or snippet file, portable like any custom code. That is a real advantage over the tag-if approach, where the content is trapped in a template a theme update can overwrite.

Want this built into your store?

This is the kind of foundation we build at Shopatch: content modelled as data your team can edit, instead of logic only a developer can safely change. If your product pages are held together by tag-if chains, we can move the content that matters into metafields and metaobjects, so adding the next product means filling in a field. Get in touch and tell us what changes per product.