Shopify Metaobjects Killed My Headless CMS in One Saturday
- Metaobjects are native Shopify content types, free on every plan, addressable from Liquid without extra API calls
- Moved 120 entries out of Contentful in 4 hours using CSV bulk import and a 40 line transform script
- Three Liquid patterns handle 90 percent of use cases, loops, handle lookups, and reference field filters
- Four real limits to know, custom rich text blocks, localization, drafts, and revision history
- Killed a 300 EUR a month subscription and cut one service from my deploy pipeline in one Saturday
I ran a headless CMS on top of my Shopify store for almost a year. Contentful on the content side, Shopify for commerce, a thin Next.js layer pulling both together. It worked. It also cost 300 EUR a month, added two services to monitor, and meant anyone editing a testimonial had to learn a second admin. Last month I migrated everything to Shopify Metaobjects and killed the subscription. Here is exactly what I did, what broke, and why I should have done this a year ago.
What Metaobjects Actually Are
Shopify Metaobjects are custom content types that live inside the store admin. Think of them as Airtable tables, except they are native to Shopify, free on every plan, and can reference products, variants, collections, and files directly.
You define a type once (Testimonial, Team Member, FAQ, Press Mention, Case Study, whatever), set the fields (single line text, rich text, file, reference, number, date, list), and suddenly the store admin has a new section where you can create and manage entries like any other Shopify resource.
The piece most tutorials skip is that Metaobjects are addressable from Liquid directly. No GraphQL, no storefront API tokens, no separate fetch layer. You write `{% for testimonial in shop.metaobjects.testimonial.values %}` and loop through them in a theme template the same way you would loop through products in a collection.
For anyone who has built a headless setup, that sentence is the whole argument. A content type that behaves like a first class Shopify object, renders inside the theme, and does not require a second service. Four months ago I would have told you this was impossible without a third party app. Now it is just a dropdown in settings.
The Migration: 120 Entries, 4 Hours
My old stack had five content types in Contentful. Testimonials (34 entries), case studies (12), team members (3), press mentions (41), and FAQ entries (30). 120 rows total, all structured, all with relationships to products or collections.
Migration took four hours end to end. Most of it was the export and field mapping, not the actual work.
Step 1. Define the types in Shopify admin. Settings, Custom data, Metaobjects, Add definition. I set up each type with the same fields as Contentful, plus a few Shopify specific ones (reference to Product for case studies, reference to Collection for testimonials grouped by product line).
Step 2. Export from Contentful as JSON. Standard export, nothing fancy. I wrote a 40 line Node script to transform the JSON into Shopify's bulk import format, which is CSV for Metaobjects.
Step 3. Import via the Shopify Bulk Data Editor. You can upload CSVs of Metaobject entries the same way you upload product CSVs. One caveat. References (like linking a testimonial to a product) need the product handle or GID, not a Contentful ID, so the transform script needed a lookup step against my Shopify product export.
Step 4. Rebuild the theme blocks. Every section that previously fetched from Contentful got rewritten to use Liquid Metaobject loops. The FAQ section went from 80 lines of JS to 20 lines of Liquid. The case studies section actually got shorter because I no longer needed error handling for a third party API that could rate limit me.
Step 5. Kill the Next.js CMS layer and redirect the content routes back to Shopify pages. Three lines in the `_redirects` file, and the switchover was live.
Net result. One admin instead of two. One service to pay for instead of three. Editors can update testimonials in the same place where they update product descriptions, which means the last friction point for the non technical side of my team disappeared.
The Liquid Patterns That Actually Ship
There are three patterns I now use constantly. They look simple in hindsight but took me a few hours of trial and error to get right.
Pattern 1. List all entries of a type.
{% for entry in shop.metaobjects.testimonial.values %}
{{ entry.quote }}
{{ entry.author }}, {{ entry.role }}
{% endfor %}
That is the whole pattern. No fetch, no await, no error state. It runs at page render time with zero extra cost and ships whatever I put in the admin.
Pattern 2. Reference a specific entry by handle.
Useful for features like a homepage hero that pulls a single featured testimonial. I set a handle on each entry (testimonial with handle `hero-quote`), then reference it directly.
{% assign hero = shop.metaobjects.testimonial['hero-quote'] %}
{{ hero.quote }}
Pattern 3. Filter by a reference field.
The one that replaced the most Contentful code. Show only testimonials linked to the current product.
{% for entry in shop.metaobjects.testimonial.values %}
{% if entry.linked_product == product %}
{{ entry.quote }}
{% endif %}
{% endfor %}
That used to be a GraphQL query with variables. Now it is a Liquid if statement. For most small to mid sized stores, that is the whole gap between "needs a headless CMS" and "fine with native Shopify."
Where Metaobjects Do Not Fit (Yet)
I am not going to pretend this replaces every CMS. Four real limits caught me during migration.
Rich text with custom blocks. Shopify's rich text field is solid for paragraphs, headings, links, and basic formatting. It does not support custom embed blocks (like callouts, tables, or custom components). For long form blog posts with varied layouts I still use the Shopify blog engine, which has its own editor. For structured content with light formatting, Metaobjects cover it.
Localization. Metaobjects support translations through the Translate & Adapt app, but the setup is heavier than what Contentful offers out of the box. If you are running a multilingual store with constant content changes in six languages, this will annoy you. For English plus one or two secondary languages, it is fine.
Preview and draft states. Metaobjects do not have a draft versus published workflow the way a content platform would. You can use a visibility boolean field as a workaround, but editorial teams used to "save as draft, request review, then publish" will feel the gap.
Revision history. No built in version history on Metaobject entries. Shopify tracks the last edit but does not let you roll back to a previous version. For anything business critical, I export the JSON once a week into the same backup process I use for products. A ten line Shopify CLI script dumps all Metaobject entries to a dated file in my repo. If a junior teammate wipes a row, I can restore from yesterday's copy in under two minutes.
Bulk editing at scale. The Shopify Bulk Editor is fine for 100 entries. It becomes painful around 1,000 and unusable around 5,000. If your use case is a product catalog with 20,000 rows of structured metadata, this is not the tool. For the kind of content most stores actually store (testimonials, team, FAQs, case studies, press), you will never hit that wall.
If any of those four matter to the business, stick with a real headless CMS. If none of them are dealbreakers, the math strongly favors moving the content back inside Shopify.
Bottom Line
Headless CMS stacks made sense when Shopify's native content tools were weak. In 2026 they are not weak anymore. Metaobjects plus Metafields plus the Bulk Editor cover most of what small and mid sized stores actually need, they ship inside the theme without extra services, and they are free on every plan.
I killed a 300 EUR a month subscription, cut a Next.js service out of my deploy pipeline, gave my editors one login instead of two, and moved from "always fetching" to "rendered at build time" for 90 percent of structured content. Four hours of migration work. Lower monthly cost forever.
The headless CMS is not dead, but for Shopify stores under a few hundred products and a handful of content types, it is very hard to justify the stack tax anymore. Run the math on your current CMS bill. If it is more than 50 EUR a month and less than 500 entries of structured content, this is the migration worth spending a Saturday on.
Back to all articles