Configuring Functions with metafields, so rules change without deploys
Short answer
Shopify Functions read their settings from metafields through their GraphQL input query. Every Function API exposes the function owner, such as the discount or validation, and its metafields; products, customers and the shop have metafields too. A single JSON metafield in an $app namespace usually holds the configuration, and an admin UI extension gives merchants a settings screen for it. Metafield values over 10,000 bytes are left out of the input.
- All Function APIs provide access to the function owner and its metafields in their GraphQL schema.
- A reserved $app prefix in the metafield namespace stops other apps from using the metafield.
- Input query variables are filled from a JSON metafield on the function owner; only JSON metafields are supported.
- Metafield values larger than 10,000 bytes are excluded from a Function’s input.
- Admin UI extensions can give validation, discount and order routing Functions a settings screen in the Shopify admin.
A Shopify Function is compiled code that runs inside checkout. If its threshold, product list or error message is written into that code, every business change becomes a developer task and a deploy. The fix is to keep the logic in the Function and the settings in metafields, where the merchant or an admin screen can change them.
How a Function sees its settings
A Function receives only the data its GraphQL input query asks for. Metafields are part of that data. Shopify’s documentation states that all Function APIs provide access to the function owner, and its metafields, as part of their schema. The owner is the object the merchant creates when they turn the Function on: a discount, a validation, a delivery customization or a payment customization.
The Cart and Checkout Validation input, for example, can read metafields on several kinds of object, including these four:
| Metafield on | Good for | Example |
|---|---|---|
The function owner (validation, discount…) | The rule’s own settings | Limits per product, tier thresholds, error text |
| Product variant | Per-product data the rule depends on | A maximum quantity, a restricted flag |
| Customer | Per-buyer data | An approved-account flag, a spending limit |
| Shop | Store-wide values shared by several Functions | A minimum order value |
Owner metafields keep one rule’s settings together, and each owner the merchant creates, such as each discount, carries its own. Resource metafields suit data the merchant already manages product by product.
Each Function API names its owner in the input, so the configuration field sits in a predictable place:
| Function API | Owner in the input | Where the merchant manages it |
|---|---|---|
| Discount | discount | The discount’s details page |
| Cart and Checkout Validation | validation | Settings > Checkout > Checkout rules |
| Delivery Customization | deliveryCustomization | Set through the app |
| Payment Customization | paymentCustomization | Set through the app |
One JSON metafield, in your own namespace
Shopify recommends a single JSON metafield when the configuration has nested or repeating data, because it is simpler to manage and to query. Use a reserved `$app` prefix in the namespace, so other apps can’t use the metafield. The Function then reads the parsed value with jsonValue:
query RunInput {
cart {
lines {
quantity
merchandise {
... on ProductVariant {
id
}
}
}
}
validation {
metafield(namespace: "$app:product-limits", key: "product-limits-values") {
jsonValue
}
}
}The metafield is written through the GraphQL Admin API, either when the owner is created (for example with discountAutomaticAppCreate) or afterwards with a metafield update.
Input query variables: settings that change the query itself
Some settings don’t just feed the logic, they decide what the Function should ask for. A rule for “products in these collections” needs the collection IDs inside the query, in fields such as inAnyCollection. Input query variables handle this: Shopify fills them by reading a JSON metafield on the function owner, where each top-level key is a variable name. The namespace and key are set in the extension’s shopify.extension.toml, under extensions.input.variables.
Two constraints: only JSON metafields are supported, and a list variable with more than 100 elements returns an error. The same variables let one Function serve several stores with different collections, without a redeploy.
Giving the merchant a settings screen
Metafields are only “no deploy” if someone other than a developer can edit them. An admin UI extension provides that screen inside the Shopify admin:
admin.settings.validation.renderfor validation Functions, which merchants reach from Settings > Checkout > Checkout rules.admin.discount-details.function-settings.renderfor discount Functions, on the discount’s details page.admin.settings.order-routing-rule.renderfor order routing rules.
The extension writes the metafield with applyMetafieldChange, and Shopify’s function settings component provides the form layout and ties into the admin’s save bar. Validate each field in that form, for example a minimum and maximum, so a merchant can’t save a threshold the Function can’t handle.
Example: per-product quantity limits
Shopify’s own tutorial for complex validation rules follows this pattern end to end. The merchant sets a maximum quantity for each product variant in an admin screen. The app stores all the limits as one JSON value in the $app:product-limits namespace on the validation. At checkout, the validation Function reads that value, compares it with the quantity of each cart line, and returns an error for any line over its limit.
The stored value is ordinary JSON that the Function parses. A simple shape maps variant IDs to limits:
{
"gid://shopify/ProductVariant/1234567890": 2,
"gid://shopify/ProductVariant/1234567891": 5
}To change a limit, the merchant edits the number in Settings > Checkout > Checkout rules and saves. No code changes and no deploy. The one thing to watch is growth: a map like this gets longer with every product, and once the value passes 10,000 bytes, the Function no longer receives it. For large catalogs, a product variant metafield per product scales better than one map on the owner.
Limits to design around
- Size. Metafield values over 10,000 bytes are excluded from the input. Split large configuration across several metafields.
- Query. The input query can be at most 3,000 bytes (comments excluded) with a maximum cost of 30, and list arguments can’t exceed 100 elements.
- Input. For carts up to 200 lines, the whole Function input is capped at 128 kB, so configuration competes with cart data for space.
- Logic. Metafields change settings, not behaviour. A new kind of rule still needs a code change and an app deploy.
Changing the configuration shape safely
Sooner or later the settings format changes: a single threshold becomes a list of tiers. Shopify’s documentation suggests the parallel change (expand and contract) pattern: the Function reads both the old and new metafields, preferring the new one, the app writes both during the transition, existing data is migrated, and the old metafield is removed once the migration is finished.
A sensible order of work
- List every value in the rule that the business might change: thresholds, lists, messages, dates.
- Decide where each lives: the owner metafield for rule settings, product, customer or shop metafields for data managed elsewhere.
- Define one JSON schema per owner, in an
$appnamespace, and version it from the start. - Build the admin settings screen with validation on each field.
- Test on a development store by changing settings, not code, and confirm checkout follows.
Where Lintel fits
Lintel designs Shopify Functions so the settings merchants change most often live in metafields with an admin screen, inside a custom app on Plus stores. For an existing Function with hard-coded values, moving them into metafields is a contained change. Validation is a common starting point; see what checkout validation can block.
Questions
- Can a merchant change a Shopify Function’s settings without a developer?
- Yes, if the Function reads its settings from metafields and the app provides an admin settings screen, for example under Settings > Checkout > Checkout rules for validation. Changing the logic itself still needs a deploy.
- Which metafields can a Function read?
- Every Function API exposes the function owner’s metafields. Depending on the API, the input can also read metafields on objects such as product variants, customers and the shop.
- How large can a Function’s configuration metafield be?
- Metafield values over 10,000 bytes are excluded from the Function’s input. Larger configuration should be split across several metafields.
