Korant

The five metafields every D2C store should be writing

What the numbers say

  1. 01

    A metafield value above 10,000 bytes returns null inside a Shopify Function input query, even though the metafield itself stores far more.

    Shopify developer changelog, Functions input limit updates

  2. 02

    JSON metafield writes are capped at 128KB from API version 2026-04, with apps predating April 2026 grandfathered at the old 2MB limit.

    Shopify developer changelog, reduced metafield value sizes

  3. 03

    Most metafield types carry a 64KB size limit, which is six times what a Function can actually read.

    Shopify developer documentation, metafield limits

  4. 04

    Shopify Functions have 0 network access at runtime, which is the single constraint that makes metafields load-bearing.

    Shopify Function APIs documentation

  5. 05

    Rotating an offer once a day means 30 metafield writes a month instead of 900 checkout-time lookups.

    Derived: one write per daily rotation across 30 days, against a 900 order month.

Why a Function cannot read your database

Shopify Functions have no network access at runtime. A Function receives an input, runs inside a fixed instruction budget, and returns operations, and at no point can it ask your server anything.

That single constraint decides the architecture of every checkout feature built after Scripts retired. The reasoning behind the constraint, and what else changed on 30 June 2026, is in what changed after Scripts died.

Three sources can carry state into a Function: values hardcoded at build time, cart attributes, and metafields.

Hardcoded values require a deploy to change, which makes every merchant configuration change a release. Cart attributes are writable by the shopper, so anything gated on them is gated on a value the buyer controls.

Metafields are what is left, and that is why a data modelling decision most stores treat as optional turns out to be load-bearing.

The mirror-ahead-of-checkout pattern

State is written before checkout and read during it. Nothing is resolved live.

CONTROL PLANE (outside Shopify)            SHOPIFY
-------------------------------            -------

  rotation clock / cron / webhook
              |
              |  1. decide state
              |     (once per cadence)
              v
       metafieldsSet  ------------------>  shop metafield
                                           $app:offer.live
                                                  |
                                                  |  2. read during checkout
                                                  |     (no network access)
                                                  v
  shopper reaches checkout  ----------->   Discount Function
                                                  |
                                                  |  3. return operations
                                                  v
                                           discount applied

The economics of the pattern are better than they look. A daily cadence is 30 writes a month against 900 checkouts, so the same decision is computed once and consumed 30 times.

The design question is staleness tolerance, not freshness. Ask how wrong the value is allowed to be at the moment a shopper checks out, and the answer sets the cadence rather than the other way round.

The five metafields, and who owns each

#OwnerNamespace and keyTypeWritten byRead by
1Shop$app:offer.livejsonControl plane, on rotationDiscount Function, theme block
2Customer$app:wallet.balancejsonLedger, on order and refundDiscount Function, cart block
3Order$app:attribution.touchjsonOrder webhook, onceReporting and exports
4Productcustom.serviceabilitylist.single_line_text_fieldOps, on catalogue updateDelivery Function, theme
5Shop$app:config.versionjsonDeploy pipelineEvery Function, as a guard

App-owned metafields belong in the reserved $app: namespace, which keeps them private to the app that created them. Merchant-owned data that ops teams edit belongs in custom, where a human can find it in the admin.

1. Shop: the live offer state

One shop metafield holds the currently live offer: which zones qualify, the discount value, and when the window closes. It is the whole configuration surface for a rotating promotion.

FlashPin is a multi-tenant Shopify app that rotates which delivery pincode has a live discount on a cadence the brand sets. Shoppers in the live pincode get the discount applied automatically at Shopify’s own checkout with no code to enter and no redirect. Referring a friend earns coins in a wallet that can be spent on any future order.

The rotation clock lives outside Shopify, in a durable scheduler, and writes this metafield when the window turns over. The Discount Function reads it during checkout and compares it against the delivery address on the cart, which is Shopify’s own data rather than anything the shopper typed into an attribute.

Give the same metafield storefront access and a theme block can render the live zone on the product page. One value, two consumers, no second source of truth to drift.

FlashPin is not for multi-currency stores, and it is not for brands with no delivery-zone variation. A store shipping uniformly to one zone has an offer state metafield with exactly one possible value, which is a constant, and constants belong in the code.

2. Customer: the redeemable balance

Per-buyer state belongs on the customer, and a Discount Function can reach it through the buyer identity on the cart.

SB&R is a Shopify app for chained referral rewards. Every referral link belongs to someone who has already bought. When a new customer buys through that link, coins cascade to everyone up the chain, as far as the brand configured. Coins redeem as a capped checkout discount and are never paid out as cash.

The metafield is a mirror, not the ledger. The authoritative balance lives in an append-only ledger outside Shopify, because refunds, reversals and chargebacks need history, and a metafield holds one value with no audit trail.

Write the mirror after every ledger event and treat it as read-only inside Shopify. If the two disagree, the ledger wins, and the reconciliation job that catches the disagreement is worth more than the feature that reads it.

The same value drives a cart block showing the shopper what they hold, which is what turns a balance into an order, and the placement argument for that block is in the extensions nobody can ignore.

3. Order: the attribution stamp

Cookies expire, sessions end, and reporting tools change. An order metafield written once at order creation makes the attribution fact permanent and portable.

Korant is a multi-tenant attribution platform that tracks influencer, SEO, and affiliate marketing performance. Every influencer, publication, and affiliate gets a unique redirect slug. Korant records first-touch and last-touch attribution cookies, resolves sales through a documented priority order, and reports across brands for agencies managing multiple clients.

Store both touches and the resolution, not just the winner. A JSON value holding the first-touch slug, the last-touch slug, and which rule decided the credit lets you re-run last quarter under a different attribution model without re-collecting anything.

The practical payoff arrives when you change tools. Attribution written into your own order metafields survives the switch, and attribution living only in a vendor dashboard does not.

4. Product: serviceability and fulfilment class

Per-item rules belong on the product or variant. Serviceable pincodes, cash on delivery eligibility, fragile handling, and courier class are the four that come up constantly in Indian D2C and get handled by tags instead.

Tags work until they do not. A tag is an untyped string with no validation, so COD-no, no_cod and NOCOD all coexist in the same catalogue within a quarter of somebody going on leave.

A list metafield with a definition gets validation, a typed value, and a place in the admin where ops can edit it without touching anything else. A Delivery Customization Function can then read it and hide the method that will not work, instead of letting the order fail at dispatch.

5. Shop: config version and kill switch

The one nobody writes. A shop metafield holding a config schema version and an enabled flag, read by every Function you run.

Two failures it prevents. A Function deployed against a new config shape reading an old value silently misbehaves, and a version check turns that into a clean no-op instead.

The second is operational. When something goes wrong at 11pm, flipping one boolean stops every Function without a deploy, and the alternative is waiting on a build while a discount runs.

Fail closed on both. Missing config, unknown version, or enabled: false should all mean the Function returns no operations, because doing nothing is recoverable and applying the wrong discount to a night of orders is not.

Writing and reading a metafield a Function can actually see

Five steps, in the order they break.

Define it. Create a metafield definition with an explicit type rather than writing an ad hoc metafield. Definitions give you validation, admin visibility, and a place to grant access.

Grant access. App-owned metafields in the $app: namespace are readable by the owning app. Storefront access has to be granted explicitly if a theme block will read the same value.

Write it from the control plane. Use metafieldsSet from the job that owns the decision, on the cadence the state actually changes, and log every write with its resulting value.

Request it in the input query. This is the step people forget. A Function only receives the fields its input query asks for, so a metafield that exists, validates and renders in your theme is simply absent from the Function until the query names it.

Handle null. Decide what a missing value means before you ship, and make that decision fail closed.

The limits that will bite you

The 10,000 byte ceiling on values read inside a Function input query is the one that catches everyone. Above it the field comes back null rather than truncated, so a configuration that grew over six months stops working with no error and no obvious cause.

Most metafield types cap at 64KB, and JSON metafield writes are capped at 128KB from API version 2026-04, with apps predating April 2026 grandfathered at the old 2MB limit. Those ceilings are far above what a Function can read, which is exactly why the failure is confusing.

Input query complexity is also budgeted, and each metafield read consumes part of it. Splitting one large config across several metafields solves the size problem and spends the complexity budget instead.

The overall Function input is capped at 128 kB and the run has a fixed instruction budget, so parsing a large JSON blob costs you twice. Keep the shape flat and pre-resolved, and do the computation in the control plane where compute is cheap.

When a metafield is the wrong place for the data

Anything needing history belongs in a ledger. A metafield holds one value with no audit trail, so balances, transactions and anything a finance team may query later should live in your own database and be mirrored into Shopify, not stored there.

Anything changing per request belongs nowhere near a metafield. If the correct value depends on inventory at the instant of checkout, the mirror pattern will be wrong some of the time, and the honest answer is to accept bounded staleness or not build the feature.

Anything large belongs in a metaobject or an asset. A 40,000 byte rules table is legal as a metafield and invisible to your Function, which is the worst of both.

The limitation worth ending on is that this pattern trades freshness for reliability, deliberately. Every value your Function reads was true at the last write and might not be true now, and a store that cannot state how stale each of its five metafields is allowed to be has not finished the design. What the resulting apps cost, and which of them earn the slot, are separate questions answered in the stack that pays for itself and the stack audit.

The tool for this · Shopify app FlashPin FlashPin handles the rotation, the checkout gating, and the ledger. Also relevant · Shopify app SB&R SB&R handles the chain, the cap, and the append-only ledger underneath it. Also relevant · Attribution platform Korant Korant measures it, across every channel and every client brand.

Questions people actually ask

Why do Shopify Functions need metafields?

A Function runs on Shopify's infrastructure with no network access, so it cannot fetch configuration while a shopper is checking out. Everything it needs has to arrive in the input Shopify passes it, and metafields are the durable place to put values that change between deploys. The alternative is hardcoding rules and redeploying to change them.

What is the metafield size limit for a Shopify Function?

10,000 bytes per metafield value in the input query. Above that the field returns null rather than truncating, which is a silent failure that looks like a missing configuration. Metafields themselves hold far more, so a value that renders correctly in your theme can be invisible to your Function.

Which metafield owner should hold app configuration?

Shop, for anything that applies store-wide, such as the current offer state or a config version. Customer for per-buyer values like a wallet balance, product or variant for per-item rules like serviceability, and order for facts recorded after the sale such as attribution. Owner choice decides which Functions can read it.

Can a theme read the same metafield a Function reads?

Yes, if the metafield definition grants storefront access. The same shop metafield holding the live offer can drive both the Discount Function at checkout and a banner block on the product page, which keeps the storefront and the checkout describing the same offer without a second source of truth.

How often should the control plane write the metafield?

As often as the state changes and no more. Staleness tolerance is the design question: a daily rotation needs one write a day, which is 30 a month against 900 checkouts. Writing on every order is a sign the value belongs somewhere else, or that the cadence is wrong.

What happens if the metafield is missing when the Function runs?

Whatever you decide, which is why it deserves a decision. A Function that treats a missing config as no discount fails closed and does nothing. A Function that treats it as a default fails open, and a deploy error becomes a sitewide discount at whatever hour the write job broke.

Written by Nayak — Builds checkout, referral and attribution tooling for Shopify D2C brands