Back to Blog

Shopify's JSON Metafield 128KB Cap: What Developers Need to Know

K
Karan Goyal
--5 min read

Shopify caps JSON metafield writes at 128KB starting April 2026. Existing apps are grandfathered at 2MB. Here's how to audit and migrate.

Shopify's JSON Metafield 128KB Cap: What Developers Need to Know

Shopify now enforces a 128KB cap on JSON metafield values. If you're storing large product configurations, multilingual content, or image galleries in metafields, you will hit this wall. Here's how to detect it, handle the error gracefully, and architect around it.

If you manage a high-complexity Shopify store — one with custom product configurators, multilingual storefronts, or data-rich catalog structures — you've probably leaned heavily on metafields to store structured JSON. For a long time, they were the flexible backbone of everything from variant matrices to personalisation logic. Then came a quiet but impactful change: the shopify metafield 128kb limit, which officially began rolling out with API version 2026-04. This article walks you through exactly what that limit means, how your app will fail if it's not prepared, and the battle-tested strategies to work around it.

The Problem: What Is the 128KB Limit?

Shopify metafields allow merchants and developers to attach arbitrary structured data to almost any resource — products, variants, orders, customers, pages, and more. Historically, the json metafield type had a relatively generous size ceiling. But beginning April 2026, Shopify capped the value size of JSON metafields at 128 kilobytes per metafield entry.

128KB sounds like a lot — and for most use cases, it genuinely is. A typical product description with a handful of structured attributes will comfortably fit in a few kilobytes. The problem surfaces at scale: when a single metafield becomes a catch-all container for everything from multi-language copy, to nested variant rules, to serialised image manifests. That's when you push past the limit fast.

To put this in concrete terms: 128KB is roughly 131,072 characters. A simple product configuration JSON with 50 variants, each with 10 attributes, will hover around 5-15KB. But once you add translated strings for 5 languages, a full image-object gallery, and nested override rules, you can easily blow past 128KB in a single metafield.

The limit applies to the serialised string value stored in the metafield — not the logical structure of your data. So even if your data makes semantic sense as a single object, Shopify only cares about how many bytes the JSON string occupies on disk.

What Happens When You Exceed the Limit?

When you attempt to write a JSON metafield value that exceeds 128KB, Shopify's Admin API returns a 422 Unprocessable Entity HTTP error for REST, or an error object within the GraphQL response body. The mutation does not partially succeed — the entire write is rejected.

Here is what the REST error looks like:

json
{
  "errors": {
    "value": [
      "is too long (maximum is 131072 characters)"
    ]
  }
}

And via the GraphQL Admin API, a failed metafields set mutation returns:

graphql
mutation metafieldsSet($metafields: [MetafieldsSetInput!]!) {
  metafieldsSet(metafields: $metafields) {
    metafields {
      id
      key
      value
    }
    userErrors {
      field
      message
      code
    }
  }
}

# Response when value exceeds 128KB:
# {
#   "data": {
#     "metafieldsSet": {
#       "metafields": null,
#       "userErrors": [
#         {
#           "field": ["metafields", "0", "value"],
#           "message": "Value is too long (maximum is 131072 characters)",
#           "code": "TOO_LONG"
#         }
#       ]
#     }
#   }
# }

The error code TOO_LONG is the one to watch for in your error-handling logic. If you're building a Shopify app that writes metafields, you should be checking userErrors on every mutation response — not just catching HTTP 4xx/5xx responses, since GraphQL returns 200 OK even on logical errors.

Common Scenarios That Trigger the Limit

Based on real-world Shopify development, these are the most frequent causes of hitting the shopify metafield 128kb limit:

  • Product variant configuration matrices. Custom product builders — think furniture configurators or apparel customisers — often store all possible variant combinations, pricing rules, and availability flags in a single JSON metafield. A product with 200+ SKUs and nested pricing tiers can easily exceed 128KB.
  • Multilingual content stores. Rather than using Shopify's built-in translation APIs or a third-party translation app, some developers embed all language variants directly into a metafield JSON object. Five languages × 20 fields × product description copy = fast KB accumulation.
  • Rich image galleries. Storing image metadata (alt text, captions, focal points, dimensions, CDN URLs) for 30+ images in a single metafield is a common pattern that blows past the limit.
  • Bundled product data. Bundle apps that store all component SKUs, quantities, discount rules, and display settings in one metafield are particularly vulnerable.
  • App-generated config blobs. Third-party apps that use metafields as their persistence layer sometimes write increasingly large config objects over time as users add more customisations — a limit no one thought about during initial development.
  • Historical data accumulation. Some developers append to a metafield JSON array over time (e.g., a log of price changes or review aggregations). Without a trim mechanism, this grows unbounded.

How to Check Metafield Sizes via the Admin API

Before migrating or refactoring, it's useful to audit your existing metafields to find which ones are approaching or exceeding the limit. Here's how to do it using the GraphQL Admin API:

graphql
# Query to fetch metafield sizes for a product
query getProductMetafields($id: ID!) {
  product(id: $id) {
    id
    title
    metafields(first: 50) {
      edges {
        node {
          id
          namespace
          key
          type
          value
        }
      }
    }
  }
}

Then, in JavaScript, you can calculate sizes and flag large ones:

javascript
const { data } = await shopify.query({
  query: GET_PRODUCT_METAFIELDS,
  variables: { id: `gid://shopify/Product/${productId}` }
});

const LIMIT_BYTES = 131072; // 128KB in bytes

const metafields = data.product.metafields.edges.map(({ node }) => ({
  key: `${node.namespace}.${node.key}`,
  type: node.type,
  sizeBytes: new TextEncoder().encode(node.value).length,
  sizeKB: (new TextEncoder().encode(node.value).length / 1024).toFixed(2),
  overLimit: new TextEncoder().encode(node.value).length > LIMIT_BYTES,
  percentUsed: ((new TextEncoder().encode(node.value).length / LIMIT_BYTES) * 100).toFixed(1)
}));

const atRisk = metafields.filter(m => parseFloat(m.percentUsed) > 70);
console.table(atRisk);

Run this audit across all your products using the Admin API's pagination (after cursor). Flag any metafields above 70% of the limit — those are your time bombs.

For bulk auditing across an entire store, use the Bulk Operations API:

graphql
mutation {
  bulkOperationRunQuery(
    query: """
    {
      products {
        edges {
          node {
            id
            title
            metafields {
              edges {
                node {
                  namespace
                  key
                  value
                }
              }
            }
          }
        }
      }
    }
    """
  ) {
    bulkOperation {
      id
      status
    }
    userErrors {
      field
      message
    }
  }
}

Workarounds: How to Store More Than 128KB

If you need to store more data than the limit allows, here are the main architectural strategies, ranked from simplest to most robust:

1. Compress the JSON Before Storing

If your JSON has a lot of repetitive keys or whitespace, you can reduce its size significantly before storing it. Use a compact serialisation and consider compression algorithms.

javascript
// Using pako (zlib-compatible) in Node.js for GZIP compression
import pako from 'pako';

function compressToBase64(obj) {
  const jsonString = JSON.stringify(obj); // No extra whitespace
  const compressed = pako.gzip(jsonString);
  return Buffer.from(compressed).toString('base64');
}

function decompressFromBase64(b64) {
  const compressed = Buffer.from(b64, 'base64');
  const decompressed = pako.ungzip(compressed, { to: 'string' });
  return JSON.parse(decompressed);
}

// Check size before storing
const payload = { /* your large JSON object */ };
const compressedValue = compressToBase64(payload);
const sizeKB = Buffer.byteLength(compressedValue, 'utf8') / 1024;
console.log(`Compressed size: ${sizeKB.toFixed(2)} KB`);

// Store as a string metafield (base64 encoded)
const metafield = {
  ownerId: `gid://shopify/Product/${productId}`,
  namespace: "custom",
  key: "config_compressed",
  type: "single_line_text_field",
  value: compressedValue
};

Note: Storing compressed binary as base64 in a single_line_text_field works, but you lose queryability and the data is opaque to Shopify's admin UI. Use this only when you control all read/write paths. Also note that base64 encoding adds ~33% overhead — your raw compressed bytes still need to fit within the limit after encoding.

2. Shard Across Multiple Metafields

If your data has a natural structure (e.g., per-language, per-variant-group, per-category), split it across multiple metafields. This is the cleanest solution when data can be logically partitioned.

javascript
// Instead of one huge metafield:
// custom.product_config => { "en": {...}, "de": {...}, "fr": {...}, "images": [...] }

// Shard into:
// custom.config_en       => { ... }
// custom.config_de       => { ... }
// custom.config_fr       => { ... }
// custom.images          => [ ... ]

const shardedMetafields = Object.entries(localizedContent).map(([locale, content]) => ({
  ownerId: `gid://shopify/Product/${productId}`,
  namespace: "custom",
  key: `config_${locale}`,
  type: "json",
  value: JSON.stringify(content)
}));

// Write all shards in one metafieldsSet call (up to 25 metafields per call)
await shopify.query({
  query: METAFIELDS_SET_MUTATION,
  variables: { metafields: shardedMetafields }
});

3. Migrate to Metaobjects

Shopify Metaobjects (GA since 2023) were purpose-built for structured data that outgrows metafield simplicity. They support typed fields, relationships, and can hold much larger total data footprints — though individual field values still have their own limits.

graphql
# Define a metaobject definition for product configurations
mutation {
  metaobjectDefinitionCreate(
    definition: {
      name: "Product Config"
      type: "product_config"
      fieldDefinitions: [
        { name: "Variant Matrix", key: "variant_matrix", type: "json" }
        { name: "Gallery", key: "gallery", type: "json" }
        { name: "Locale EN", key: "locale_en", type: "json" }
        { name: "Locale DE", key: "locale_de", type: "json" }
      ]
    }
  ) {
    metaobjectDefinition {
      id
      type
    }
    userErrors {
      field
      message
    }
  }
}

Then reference the metaobject from your product via a metaobject_reference metafield. This gives you a relational structure where each field can hold up to 128KB independently — effectively multiplying your storage capacity while keeping data organised.

4. External Storage with a Reference

For truly large datasets (think thousands of KB), the right answer is to store the data externally and keep only a reference in the metafield. This is the most scalable approach.

javascript
// Store large data in an external service (e.g., your own API, S3, Redis)
async function storeExternalConfig(productId, configData) {
  const response = await fetch(`https://your-app.com/api/configs/${productId}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_KEY}` },
    body: JSON.stringify(configData)
  });
  const { url, checksum } = await response.json();
  return { url, checksum };
}

// Then store only the reference in Shopify
async function updateProductMetafield(productId, configData) {
  const { url, checksum } = await storeExternalConfig(productId, configData);
  
  return shopify.query({
    query: METAFIELDS_SET_MUTATION,
    variables: {
      metafields: [{
        ownerId: `gid://shopify/Product/${productId}`,
        namespace: "custom",
        key: "config_ref",
        type: "json",
        value: JSON.stringify({
          url,
          checksum,
          updatedAt: new Date().toISOString(),
          version: "2.0"
        })
      }]
    }
  });
}

Reading Metafields in Liquid and Storefront

When reading sharded or compressed metafields in Liquid, you'll need to access and merge them at render time. Here are practical patterns:

liquid
{% comment %}
  Reading a sharded locale config in Liquid
{% endcomment %}

{%- assign locale_key = 'config_' | append: request.locale.iso_code -%}
{%- assign locale_config = product.metafields.custom[locale_key] -%}

{% if locale_config != blank %}
  {%- assign config = locale_config.value -%}
  <div class="product-config">
    <h2>{{ config.headline }}</h2>
    <p>{{ config.description }}</p>
    
    {% for feature in config.features %}
      <li>{{ feature }}</li>
    {% endfor %}
  </div>
{% endif %}

{% comment %}
  Reading an image gallery metafield
{% endcomment %}
{%- assign gallery = product.metafields.custom.images.value -%}
{% if gallery %}
  <div class="product-gallery">
    {% for image in gallery %}
      <img 
        src="{{ image.url }}" 
        alt="{{ image.alt }}"
        width="{{ image.width }}"
        height="{{ image.height }}"
        loading="lazy"
      >
    {% endfor %}
  </div>
{% endif %}

For Storefront API access (e.g., in a headless build), use the Storefront GraphQL API to query metafields:

graphql
# Storefront API query for product metafields
query getProduct($handle: String!) {
  product(handle: $handle) {
    id
    title
    configEn: metafield(namespace: "custom", key: "config_en") {
      value
    }
    configDe: metafield(namespace: "custom", key: "config_de") {
      value
    }
    images: metafield(namespace: "custom", key: "images") {
      value
    }
    variantMatrix: metafield(namespace: "custom", key: "variant_matrix") {
      value
    }
  }
}
javascript
// Client-side merging of sharded config
async function getProductConfig(handle, locale = 'en') {
  const { data } = await storefrontClient.query({
    query: GET_PRODUCT_QUERY,
    variables: { handle }
  });

  const product = data.product;
  const config = product[`config${locale.charAt(0).toUpperCase() + locale.slice(1)}`];
  const images = product.images;
  const variantMatrix = product.variantMatrix;

  return {
    localizedContent: config ? JSON.parse(config.value) : null,
    gallery: images ? JSON.parse(images.value) : [],
    variants: variantMatrix ? JSON.parse(variantMatrix.value) : {}
  };
}

Performance Considerations

The 128KB limit isn't just a Shopify constraint — it has real performance implications you should architect for, regardless of the limit.

  • Network transfer costs money and time. Even if you could store 1MB in a metafield, fetching it on every product page load would measurably slow your storefront. A 128KB JSON payload on a mobile connection at 1Mbps takes 1 second just to download — before parsing.
  • JSON parsing is CPU-bound. Parsing a 128KB JSON object in a browser's main thread blocks rendering. Always parse in a Web Worker or use streaming JSON parsing for large payloads.
  • Cache aggressively. Metafield values don't change often. Use Shopify's CDN edge caching via the Storefront API, and layer your own caching (Redis, Cloudflare KV) in front of Admin API calls. A metafield read shouldn't hit Shopify's servers on every page view.
  • Fetch only what you need. With sharded metafields, you can load only the locale-specific or section-specific data the current page needs. Don't load all shards eagerly — lazy load based on user interaction.
  • Avoid write amplification. If you're splitting data across 5 shards and all 5 need to be written on every update, you've created write amplification. Design your shards to be independently updatable so a change to one locale doesn't require rewriting all locales.

A good rule of thumb: treat each metafield read as if it costs money and takes time (it does). Design for the minimum data transfer needed to render a page, not maximum data availability.

A Brief History: From 16KB to 128KB

To understand why this limit exists, it helps to know where it came from. Prior to 2023, Shopify's JSON metafield limit was just 16KB — barely enough for a moderate product configuration. That limit was a constant source of pain for developers building sophisticated catalog structures.

Shopify significantly increased the limit to 128KB as part of ongoing platform improvements, acknowledging that modern commerce apps require far more structured data than simple key-value stores. This 8× increase was substantial and resolved the majority of real-world use cases that were previously impossible.

The catch is that the 128KB limit is now strictly enforced via the API with explicit error responses, where previously enforcement may have been inconsistent. Apps that were silently failing or relying on undocumented headroom now receive hard rejections. This is the right behaviour — predictable failure modes are better than silent data truncation — but it requires proactive handling in any app that writes JSON metafields.

Looking forward, Shopify hasn't announced plans to increase the limit further. The direction appears to be towards Metaobjects for complex relational data, and external storage for truly large datasets. If you're architecting a new Shopify app in 2026, design from the start with Metaobjects as your primary structured data primitive, and use JSON metafields for lightweight config only.

Conclusion

The shopify metafield 128kb limit is a constraint that rewards good data architecture. If you hit it, it's usually a signal that you're trying to do too much in a single metafield. The right response isn't to find a clever workaround to cram more data in — it's to decompose your data model.

Here's a quick decision framework for your next Shopify data architecture:

  • Under 10KB, single coherent object → Use a single JSON metafield
  • 10KB–128KB, single concern → Use a single JSON metafield, monitor size
  • Logically separable sections (by locale, by category) → Shard across multiple metafields
  • Rich relational structure with multiple typed fields → Use Metaobjects
  • Large binary/media data or >1MB → External storage with a reference metafield

The move to Metaobjects in particular is worth investing time in. They're more queryable, have built-in type safety, and Shopify's tooling (including the admin UI and Flow) is increasingly built around them. For any new structured data requirement, Metaobjects should be your default choice — with JSON metafields reserved for simple, bounded config values.

Start your audit today: run the size-check query, flag anything above 70KB, and plan your migration before the limit bites you in production.

Tags

#shopify#shopify#metafields#metafields#api#api#json#json#migration#migration#performance#performance

Share this article

📬 Get notified about new tools & tutorials

No spam. Unsubscribe anytime.

Comments (0)

Leave a Comment

0/2000

No comments yet. Be the first to share your thoughts!