Or: the difference between data you own and data an app rents you. We learned this one the annoying way.
The uninstall test
Every catalog we open, we run the same test. Uninstall every app, on paper, and count what survives.
The products survive. Shopify owns those outright. Then the list gets uncomfortable. Warranty terms live in an app. Size charts live in an app. The product relationships somebody built by hand, link by careful link, live in an app. All of it sits in a vendor's database, behind the vendor's API boundary, and when the app goes, none of it comes with you.
Ask a brand where that data would go if the app disappeared tomorrow and the answer, most of the time, is silence. Not evasion. They genuinely do not know, because nobody decided where it would live. An app decided, quietly, at install time.
Switching vendors makes it worse, not better. The new enrichment tool cannot read what the old one wrote, so the catalog splits into layers of stranded data, one layer per abandoned app. We have watched brands re-enter size charts they had already built once. Our team has a name for this. Spreadsheet archaeology.
We blamed the wrong thing
Our first theory was sloppy setup. Somebody skipped the export step, somebody never read the docs, the usual mess. Fixable with discipline, we figured, and we said so with some confidence.
Then we ran the test on catalogs configured by careful, technical teams and got the same silence. It wasn't discipline. The data was doing exactly what its storage model told it to do.
Shopify attaches an ownership class to every piece of custom data, and that class, not anyone's diligence, decides what survives an uninstall. It gets decided the moment a field is created. Nobody reads it until the day it matters.
Owner of record
The native layer for custom data is the metafield: a key-value pair attached to a product, variant, collection, customer, or order. Each one has a namespace, a key, and a type. Each one also has an owner, which is the part the docs bury and the part that decides everything.
Merchant-owned metafields sit in open namespaces like custom. Every app on the shop can read and write them, you can edit them in the admin, and uninstalling an app leaves them untouched.
App-owned metafields sit in reserved namespaces like $app:warranty. Only the owning app writes there. Uninstall it and whatever lived in that namespace is on the app's terms from then on. Shopify's own uninstall guidance says to export anything you need first, because some data cannot be recovered.
App-data metafields live on the app installation itself, hidden from your admin entirely, readable only by the app that owns them.
Three classes, one admin, and only the first one is yours.

Tables you invent
Metafields attach single attributes to things that already exist. Metaobjects go further and let you define whole new structures: a definition (the schema, with fields, types, validation), then entries (instances with their own values), then a reference metafield linking entries to products.
Size charts are the demo we reach for. Define a "size_chart" metaobject with garment type, size range, measurements. Create one entry per guide the brand publishes. Reference entries from products, and the storefront renders size charts without anyone pasting tables into product descriptions.

Nerd detail, skip freely: most metafield values cap at 64KB, the json type gets 128KB on API versions 2026-04 and later, and writes go through one GraphQL mutation, metafieldsSet, which sets up to 25 fields atomically. One fails, all roll back.
What the boring answer buys you
Put catalog data in merchant-owned metafields and the uninstall test stops being scary. An enrichment pipeline can read and write that layer with zero apps in the loop. Add a third-party tool later and it reads the same fields. No export negotiation, no archaeology.
The fences are wide, too. A single metaobject definition holds up to 1,000,000 entries on every plan; until October 2025, non-Plus stores were capped at 64,000. The room exists now. Most catalogs will never touch the walls.
Code is your IP. Metafield namespaces are not, so keep them open.
What we're keeping
Three rules came out of this.
Run the uninstall test before you trust a stack, not after an app forces the question.
Prefer merchant-owned namespaces structurally, before any tool gets picked. An app that will only write to its own reserved namespace is quoting you a tenancy agreement.
Portability is decided at schema time, not migration time. By the time you are exporting, you have already lost.
Next catalog we open, the ritual runs again. Uninstall everything, on paper. Count survivors. The answer we are building toward is short.
Everything stays.
If you would rather run that count on your own catalog with us, that is exactly what our Infrastructure Stress-Test is for.
Sources
- About metafields: Core metafields documentation, ownership models, types.
- About metaobjects: Metaobject definitions, entries, ownership, and use cases.
- Metafield limits: Size limits (64KB standard, 128KB JSON) and definition count limits.
- metafieldsSet (GraphQL Admin API): The 25-metafield batch limit and atomic behavior of the mutation.
- JSON metafield values limited to 128KB: 2026-04 API version constraint on JSON metafield size.
- Increased limits for metafields and metaobjects: Metaobject entry limit unified to 1,000,000 across all plans.





Share:
The limit moved. The wall didn't.
The $90,000 line item nobody has ever added up