Shopify functions

The 10,000-Byte Metafield Trap: When Your Discount Function Silently Stops

You built the right architecture: business rules in a JSON metafield, a Discount Function that reads them, no redeploys when a promotion changes. Then the merchant adds one pricing tier too many — and every discount stops applying. No error at checkout. Nothing in the order. Here’s why, and the design that survives it.

This is part 2 of our Shopify Functions in production series. Part 1 covered how to reject discount codes cleanly. This one is about a failure mode that costs money precisely because nothing looks broken.

The scenario nobody warns you about

The standard advice for maintainable Shopify Functions is right: separate the business rules from the code. Instead of hardcoding “VIP customers get 15% off over $100,” you put the rules in a JSON metafield (or an app-owned metaobject) and have the Function read them through its input query. When the merchant wants a new tier, they edit the config — no developer, no redeploy.

It works beautifully in testing. It works in production for months. Then one day the merchant adds their fortieth pricing tier, saves the config, and every discount on the store silently stops applying.

The merchant notices when a customer emails to ask where their VIP discount went — or worse, when the month’s revenue report shows the discount line at zero.

The two limits behind it

Both are documented on Shopify’s Functions API page, but buried in a limits table that most people read once, during setup, and forget.

Limit 1: the input query is static and capped at 3,000 bytes. Your Function’s input query is fixed at build time (run.graphql). You cannot add a new field at runtime every time the merchant invents a new rule type — the query would outgrow the cap. This is exactly why the config-as-JSON-blob pattern exists: one metafield field in the query, arbitrary structure inside the JSON. So far, so good.

Limit 2: a metafield value over 10,000 bytes is not returned. This is the trap. From Shopify’s docs: “Metafields with values exceeding 10,000 bytes in size will not be returned.” Not truncated — absent. The metafield node comes back empty, as if it were never set.

Now follow the failure through your Function:

  1. The config JSON grows past 10,000 bytes (a few dozen pricing tiers with conditions gets there faster than you’d think).
  2. The input query asks for the metafield; Shopify withholds it.
  3. Your Function sees “no config” and does whatever it does when unconfigured — usually: parse nothing, match nothing, return an empty operations list.
  4. The discount is an automatic discount backed by this Function, so the store simply stops discounting. Checkout is valid. Orders complete. Nobody is notified.

The failure is silent at every layer. And your last line of defense — Function logs — can’t save you: logs are capped at 1 kB written, truncated.

The design that survives

You don’t have to abandon the config-in-metafield pattern — it’s still the right one, and Shopify’s own best practices recommend JSON metafields for complex configurations. You have to make it size-aware and fail-loud.

1. Budget the bytes, and measure them. 10,000 bytes is your hard ceiling; treat ~8,000 as your alert line. The admin UI where merchants edit the config should display the current byte size of the JSON, not just a save button. A one-line JSON.stringify(config).length (well, new TextEncoder().encode(json).length — count bytes, not characters; Chinese characters in rule names are 3 bytes each) is the difference between “we knew” and “a customer told us.”

2. Fail loud, not silent. Decide what the Function should do when the config metafield comes back absent, and make “absent” distinguishable from “legitimately empty.” Options: apply a safe default discount and have your app backend raise an alert, or apply nothing but fire a notification through your app’s own monitoring (a daily job that reads the metafield via the Admin API — where it is returned — and checks its size). The worst option is the default one: shrug and return empty operations.

3. Shard before you hit the wall. If your rules genuinely need more than ~10 KB, split the config by domain — one metafield for VIP tiers, one for quantity breaks, one for B2B rules — under your reserved $app: namespace, and query them as separate fields. Three 4 KB metafields all come back; one 12 KB metafield comes back as nothing. Remember the query-side caps while you shard: list arguments max out at 100 elements and the input query has a calculated cost limit of 30.

4. Test the boundary, not just the happy path. Add a fixture config that’s deliberately over 10,000 bytes to your dev store test suite. The behavior you want to lock in is “we get alerted,” not “discounts vanish quietly.”

How to check whether you’re exposed today

If you have a Discount Function in production reading config from metafields:

  1. Via the Admin GraphQL API, fetch the config metafield and measure the byte length of value. (The Admin API returns the full value regardless of size — the 10,000-byte cutoff applies to Function input queries, not to you.)
  2. If you’re under 8 KB: add the size display and the alert, and put a calendar reminder to re-check as rule count grows.
  3. If you’re over: your discounts may already be intermittent — the metafield only crosses the line when someone saves a larger config. Check recent orders for missing discount applications, then shard.

Where an agent fits

Everything above is a one-time design fix. The ongoing risk is drift: rule counts creep up, someone pastes a giant JSON into the metafield, a new promotion type doubles the config size. That’s a monitoring loop, and it’s the kind of thing a store operations agent can own: watch the config size, watch the rate of orders with discounts applied, and flag the anomaly to the owner with the evidence attached — before a customer does.


Verified against Shopify’s Functions API documentation (input query limits, fixed limits) as of July 2026. Field report via a Shopify Community thread on Functions maintainability — the practitioner who documented this failure mode had the numbers exactly right.