Shopify's 128KB JSON Metafield Limit Is Breaking Page Builders and Product Customizers: How to Audit and Fix It
Shopify capped JSON metafield writes at 128KB starting with API version 2026-04, down from 2MB. Page builders, bundle configs, and 360° spin data are hitting the wall with a confusing 'value exceeds maximum size' error. Here's how to find which fields are at risk and fix them before your next API upgrade.
If you build page sections, product customizers, or 360° spin viewers on top of Shopify metafields, a change that landed quietly in the developer changelog on February 19, 2026 is now showing up as production writes failing for real merchants. Starting with API version 2026-04, JSON-type metafield writes are capped at 128KB — down from a previous ceiling of 2MB. Every other metafield type is capped at 64KB. The change is not going away, and it is not something you can appeal your way out of unless you're a new app requesting an exception.
What makes this one nasty is that it doesn't fail at deploy time or show up in a linter. It fails the first time a specific field crosses the new threshold, on whatever API version your app happens to be calling that day — which means a mutation that has worked fine for a year can start throwing without any code change on your end.
What actually changed
- JSON metafields: capped at 128KB on API version 2026-04 and later. Shopify's first proposal, posted in February 2026, was a much harsher 16KB — that got revised to 128KB after community pushback before the change shipped.
- All other metafield types (text, rich text, url, number, references, lists): capped at 64KB.
idandurltypes have their own separate 2KB ceiling. - Grandfathering: apps that were already writing JSON metafield values before April 1, 2026 keep the old 2MB limit. New apps and new usage after that date get the 128KB cap, full stop — a request form exists for a case-by-case exception, but it is not a fast path.
- Reads are unaffected: values that already exceed 128KB remain fully readable on every API version, including future ones. This is purely a write-side restriction, which is exactly why it surprises people — the storefront keeps rendering the oversized field for months until someone tries to edit it.
- Scope: the limit applies across the Admin GraphQL API, Admin REST API, Customer Account API, and Storefront API, for any request made on API version 2026-04 or later.
Shopify's stated reasoning is storefront performance, not an arbitrary restriction: oversized JSON metafields — page-builder configs, bundle pricing logic, spin-viewer frame arrays — get fetched in full every time a product loads through the Storefront API, and a handful of multi-hundred-KB fields on one product page can add tens of megabytes to a single request.
The confusing part: it looks like a bug, not a limit
A merchant on the Shopify community forum ran into this on metaobjectUpdate after moving to API version 2026-04: the mutation rejected a small edit to a spins field with Value exceeds the maximum size of 131072 bytes. Current size is 317389 bytes. The confusing part was that the edit itself was tiny — but the check isn't against the size of your diff, it's against the total size of the field's stored value after the write. If a JSON blob has been quietly growing for months (more spin frames appended, more variant configs stacked in), the write that finally tips it over 128KB can be a completely unrelated, small edit. Switching that request back to API version 2026-01 made the same mutation succeed immediately, confirming it wasn't a payload bug — it was the version-gated limit.
That's the trap: nothing about your code changes when this starts failing. What changes is the API version your app (or one of your apps, if a theme app extension or a private script bumps its own version independently) happens to be calling on that request.
Step 1: find out which of your fields are already over the line
Don't wait for a write to fail in production. Query your existing JSON metafields and metaobject fields for their current byte size before you touch your API version pin. This is the same bulk-extraction pattern worth using for any large-catalog GraphQL audit — page through products rather than trying to load everything synchronously:
query AuditJsonMetafields($cursor: String) {
products(first: 100, after: $cursor) {
pageInfo {
hasNextPage
endCursor
}
nodes {
id
handle
metafields(first: 20, namespace: "custom") {
nodes {
key
type
value
}
}
}
}
}
const BYTE_LIMIT = 128 * 1024; // 131072
for (const product of allProducts) {
for (const field of product.metafields.nodes) {
if (field.type !== "json") continue;
const byteSize = Buffer.byteLength(field.value, "utf8");
if (byteSize > BYTE_LIMIT * 0.8) {
console.log(
`${product.handle} / ${field.key}: ${byteSize} bytes (${((byteSize / BYTE_LIMIT) * 100).toFixed(0)}% of limit)`
);
}
}
}
Flag anything above 80% of the limit, not just fields already over it — those are the ones that will fail on the next routine content edit, not on the API upgrade itself.
Step 2: confirm whether you're actually grandfathered
"We were already using JSON metafields before April 1, 2026" is necessary but not sufficient to assume safety. The grandfathering is tied to the requesting app continuing to call an API version where the old limit applies — the moment that app (or a workflow, migration script, or app extension acting on its behalf) makes a write on API version 2026-04 or later, that request is subject to the new cap regardless of how old the app is. If you have more than one integration writing to the same metafield — a theme, a custom app, and a third-party page builder, for instance — check each one's pinned API version independently. One of them upgrading is enough to break the field for all of them.
Step 3: fix the fields that are actually oversized
Three realistic options, roughly in order of how much you should trust them as a permanent fix rather than a stopgap:
Option A — split one JSON blob into several smaller metafields
If a single field mixes unrelated concerns (say, a page-builder config that bundles hero content, testimonials, and a product FAQ into one object), split it along those seams into separate metafields and adjust your read logic to merge them at render time.
await adminGraphql(SET_METAFIELDS_MUTATION, {
metafields: [
{ ownerId: productId, namespace: "builder", key: "hero_config", type: "json", value: JSON.stringify(hero) },
{ ownerId: productId, namespace: "builder", key: "testimonial_config", type: "json", value: JSON.stringify(testimonials) },
{ ownerId: productId, namespace: "builder", key: "faq_config", type: "json", value: JSON.stringify(faq) },
],
});
This buys headroom fast, but treat it as transitional — if the underlying data model is still one big blob logically, you're just postponing the next time a section crosses 128KB, not fixing the architecture.
Option B — move structured, repeatable data into metaobjects
If the oversized field is really a list of similar records (spin-viewer frames, bundle line items, variant-specific configs), a metaobject per record with proper field types is a better long-term fit than one giant JSON array — you get individual records under the per-field limit instead of one field holding all of them. This is the same shift we cover for tag-sprawl migrations, and the size math works the same way here: many small typed fields instead of one large opaque one.
Option C — move genuinely large payloads out of metafields entirely
For content that doesn't need to be queried or filtered — a full 360° spin frame manifest, a large page-builder theme export — store it as a JSON file via the Files API and keep only the file reference in the metafield:
mutation UploadConfigFile($input: [StagedUploadInput!]!) {
stagedUploadsCreate(input: $input) {
stagedTargets {
url
resourceUrl
parameters {
name
value
}
}
userErrors {
field
message
}
}
}
// 1. Request a staged upload target, 2. PUT/POST the JSON file to it,
// 3. Reference the resulting file with a file_reference metafield instead
// of storing the payload inline.
await adminGraphql(SET_METAFIELDS_MUTATION, {
metafields: [
{
ownerId: productId,
namespace: "custom",
key: "spin_manifest_file",
type: "file_reference",
value: uploadedFile.id, // gid://shopify/GenericFile/...
},
],
});
Your storefront then fetches the file URL on demand instead of pulling the whole payload through every product query — which also solves the performance problem Shopify cited as the reason for the limit in the first place.
Step 4: pin your API version deliberately before you audit, not after
If you haven't explicitly moved to 2026-04 or later yet, don't let it happen implicitly through an app or library auto-upgrading its default version. Run the audit query above against your current version first, fix anything over 80% of the limit, then upgrade on purpose — not the other way around, where the upgrade itself is what surfaces the failing writes in production.
Code Kaarigari audits metafield and metaobject usage across theme, app, and headless integrations, and can rebuild oversized JSON structures into a schema that survives the next Shopify size or performance change, not just this one. Start with our Shopify development services, read how a related tag-and-taxonomy cleanup plays out in the metaobject migration blueprint, or contact Code Kaarigari for a metafield size audit before your next API version bump.