How To Add Metafields in Shopify Without Apps

Poke about in the documentation for Shopify long enough and you'll end up in metafields. Metafields promise to unlock more than just a single heading and WYSIWTF panel's worth of unique content on each page of your Shopify store.

In June 2021, Shopify launched Webstore 2.0, a suite of changes to Shopify which is backwardly compatible with existing Shopify themes. Part of these changes makes the process of using Metafields without apps significantly simpler. As a result, many of the techniques outlined in this guide are no longer required. We have a new guide which shows you how to create an FAQ section on product pages without apps which you might find useful.

However, your excitement will quickly be dashed, when you realise that in order to actually add any data into a metafield, you need to build (and host) your own app, or pay a monthly fee for a third-party one.

But what if (bear with me here) you were particularly stubborn, and wanted to dabble in metafields, but not in app development? Boy, are you in the right place, buddy.

Creating metafields

Hidden away in the depths of the Shopify documentation is a trick for using URL querystrings to open up the bulk editor with new metafields available to edit (it's right at the bottom of the page).

It works kinda like this:

A diagram of how a Shopify bulk edit url is built
The anatomy of a Shopify bulk edit URL.

So it's possible to edit metadata fields just by hacking away at query strings. But that's a miserable experience for anyone, no matter how much they love a CLI. When I first saw the URL above, I figured I could build a form which (via a get request) could produce the same thing. It's a little more complicated than that, but not that much more.

Here's the data I'd need to capture:

  • What type of content the metafields are being attached to
  • A namespace, so we can group similar metafields together
  • key names, for the data we're capturing
  • What type of data it is

View metafield-add on Github

This'll also need some JavaScript, to concatenate together the different strings into the URL to generate new fields. As the metafield creation form is only used on one page, the JavaScript is inline at the bottom of the file.

Completing this form will send you to Shopify's bulk editor, but rather than editing the built-in fields which are part of products and pages by default, it allows you to edit any number of arbitrary fields of your own devising.

Structuring your data

Let's say you had a series of frequently asked questions about a particular product. You could structure your data like this:

faq (namespace)

  • q-one Is this product suitable for vegans?
  • a-one Yes. This is a mug, but is not made from bone china.
  • q-two Is this product suitable for children?
  • a-two No. The slogan on the mug is No. 1 Dad and this is not a responsibility children are qualified to undertake.

Hold on a moment, you're probably thinking, why not use numbers instead of one and two? While it's possible to get to the bulk editing screen using numbers, any data you enter against such a field will not be saved. That's why some of the inputs have that sneaky pattern attribute, to stop you from adding them.

What if I need to edit form fields in the future?

If you attempt to add a new metafield which already has data against it, you'll simply see that data in place, once you reach the bulk editor.

The URL to batch edit the same set of meta-data will remain the same. Rather than filling out the form each time, why not include it on the site, as a link? This isn't quite as daft as it sounds, as you can hide this link for all users except those who are logged into the admin interface of Shopify (they're also the only group of users who will also be able to edit the content).

Adding in content just for Shopify admin users isn't out-of-the-box Shopify functionality, so there may come a time when this method stops working, but for now, this is how to do it.

In layout/theme.liquid is the following snippet of code:

{{ content_for_header }}

This generates a bunch of meta-tags and JavaScript intended to sit within the head of your site controlling various aspects of Shopify's functionality. By turning this into a string and looking for patterns within it, we can work out if the current user is an admin or not.

Luckily, {{ content_for_header }} is global which means we're not just limited to using it in theme.liquid.

I've made this into another snippet in Mannequin, which you can find on Github. Note that it returns a string, rather than a boolean and one with whitespace around it too. This means we need to do a little work, before it's useful:

{% capture tempadmin %}{% render 'admin-check' %}{% endcapture %}
{% assign admin = tempadmin | strip %}

Because you (mostly) can't change the values of variables in Liquid, we first assign a temporary variable the value of the admin check snippet. This then gets stripped of its whitespace, when it's assigned to the real variable, admin. We use admin like this:

{% if admin == 'true' %}
    <a href="/admin/bulk?resource_name=Product&edit=metafields.faq.q-one%3Astring%2Cmetafields.faq.a-one%3Astring%2Cmetafields.faq.q-two%3Astring%2Cmetafields.faq.a-two%3Astring%2Cmetafields.faq.q-three%3Astring%2Cmetafields.faq.a-three%3Astring%2Cmetafields.faq.q-four%3Astring%2Cmetafields.faq.a-four%3Astring">
      Edit metadata
{% endif %}

That big, ugly URL is what is generated by the form we made earlier. Because this markup only appears in product.liquid, the URL is specific to that set of meta-data. If you wanted to add an edit link for another set of meta-data, you could generate one using the add metafield form.

What if I want to expand the set of captures metafields at a later date?

The querystring which the metafield form generates goes both ways - it can be passed to the Shopify bulk editor, to generate a spreadsheet-like way of editing content, but it can also be passed back to the metafield form, in order to pre-populate it with the correct values.

If you wanted to add a fifth FAQ question, you could take the original querystring and add it to the end of wherever you store your metafield page, make the adjustments to the form, then generate a new URL. Content authors who use this will see all of the existing data, plus new cells for them to add the new FAQ question and answer.

How can I see the data which is saved against content elements?

I'm so glad you asked. The Shopify documentation points at URLs which will show you the JSON for individual elements. But that would be more URL hacking. Luckily, however, Mannequin has another snippet for viewing existing metafield data.

View metafield-view on Github

This form will create five select boxes which contain a list of (respectively) your pages (specifically, all the pages which appear in linklists), your products, your blog articles, your collections and your product variants.

The snippet which lists the articles needs a tiny bit of configuration, around this line:

This should be adjusted to be a comma-delimited list of all of the blogs in your site (if you have more than one). For example:

{% assign all_blogs = "news,how-to" | split: "," %}

There's some inline JavaScript, which redirects content authors to the correct JSON file.

This JavaScript loops through the values of the select boxes (marked with a data-js="auto" attribute) until it finds one where the user has made a selection. It takes the id from that select box, plus the value (which, confusingly, is the id of that particular content item), then passes them onto a function called bounce().

Wait, what about performance?

Shopify stores can have thousands of products, with some of those having perhaps ten variations. This will generate some huge select boxes. While this isn't a page intended for your customers, performance may still be an issue. This is why each select box appears as a separate snippet, allowing you to split them out onto different pages, if required (remember to include metafield-view.js on that page, if you do this).

Is this secure?

While you probably don't want your customers stumbling upon your metafield editing pages, all those pages are doing is generating links which point to the /admin/ section of your site. Even if your customers (or competitors) did stumble upon this page, they'd still need valid credentials to either see or edit any of your data. The only information you're giving out is a list of your products, plus their identification numbers, but I assume your catalog page already does that.

You could also hide all of the content behind the admin variable, so that your customers would just see a blank page:

{% if admin == 'true' %}
  (metafield forms go here)
{% endif %}

So what does this do, exactly?

This allows you to see all of the metafields for a particular content item, in JSON format, or as I like to think of it, the poor man's XML.

We can, in theory, add metafields to a lot more types of content than the five this form allows you to see. Specifically:

  • Blog (because you can have more than one blog on your site)
  • Customer
  • Draft Order
  • Order
  • Product Image

However, during my testing, I couldn't successfully add metafields to all of these types of data, which suggests it's impossible, using just the URL interface.

How do I get this to appear in the front end?

So this is all great fun, but what use is it, unless we can burp this content out at the user? Let's have a look at some example JSON generated by metafields:

  "metafields": [
          "id": 12345678901233,
          "namespace": "faq",
          "key": "q-one",
          "value": "Is this suitable for a man over fifty?",
          "value_type": "string",
          "description": null,
          "owner_id": 1234567890123,
          "created_at": "2020-12-08T11:01:54-05:00",
          "updated_at": "2020-12-08T11:01:54-05:00",
          "owner_resource": "product"
          "id": 12345678901234,
          "namespace": "faq",
          "key": "a-one",
          "value": "Yes - there are no offensive gifts inside",
          "value_type": "string",
          "description": null,
          "owner_id": 1234567890123,
          "created_at": "2020-12-08T11:01:54-05:00",
          "updated_at": "2020-12-08T11:01:54-05:00",
          "owner_resource": "product"

We could edit our product.liquid template to reflect this on the front end like this:

{% comment %}
Check if there's anything in our FAQ namespace
{% endcomment %}
{% if product.metafields.faq.size > 0 %}

  {% unless product.metafields.faq.q-one == blank %}
    <h2>{{ product.metafields.faq.q-one }}</h2>
  {% endunless %}

  {% unless product.metafields.faq.a-one == blank %}
    <p>{{ product.metafields.faq.a-one }}</p>
  {% endunless %}

{% endif %}

(I enjoy the perversity of using unless tags, because every other language just makes use of if)

Let's take a closer look at the business-end of that liquid tag:

A diagramof how FAQ data is retrieved using liquid tag in Shopify
Breakdown of the liquid tag used to retrieve FAQ data

Here's another example: what if you added an image metafield to pages, in the global namespace? The tag for that would be:

OK, I've done that. Now how do I delete metadata?

We've been using the get method of forms to sort of fake API access up until now. The get and post form methods mirror HTTP functionality, which also includes another method, delete. There was a brief moment when Firefox supported the delete method. But it looks like it's not going to become part of a standard anytime soon.

However, if you edit an old metafield and then remove its content, it will disappear from the JSON.


Mannequin doesn't have any dependencies outside of the repo, but the metafields functionality is quite modular. This was done to allow it to be spread over several pages, if required. The files are as follows: