Storefront Library
Background, Motivation and Requirements​
The Storefront Library was developed as part of the Checkout 2.0 project.
Background: With Checkout 2.0, the discount capabilities were significantly expanded and thus became more complex. Differences compared to the old discount system using Shopify Scripts include:
-
Discounts can also be applied based on variants.
-
For free products, any number of thresholds are possible. Similarly, multiple different products can be linked at a threshold using "and" or "or" logic.
-
With "conditional Discounts", a new type of discount was introduced that can cover cases such as:
- Tiered pricing
- BOGO (Buy one get one free)
- Buy two products, get one free.
- Buy one product, get x% discount on another product.
- Buy Product A for €50, get 70% discount on Product B.
- 40% discount on the first three units of a product, but only the standard discount of 10% from the fourth unit onwards.
- Four for three: Buy four products, get the cheapest one free.
To display strikethrough prices in categories and on products after entering a discount code, a tool was needed that can also determine the strikethrough prices for the new discount types correctly and reliably. Everything that is taken into account in the checkout must be considered.
Due to the new complexity, it was decided from the beginning to avoid requiring a storefront developer to learn about discounting when they need to create functionality related to discounts. Therefore, it was decided to develop a library that can be used by anyone as a black box. This means: The developer can call the library's functions without having to worry about the details of the calculations. As a result, all functions needed in the discount context are in one place, avoiding a patchwork that is difficult to oversee and maintain.
Specifically, the following functions related to discounting were needed:
- Setting, changing and removing discount codes.
- Calculation of the discounted product price for displaying the strikethrough price.
- Separate query of conditional discounts for a product.
- Information for the free product assignment process:
- Determination of thresholds.
- Determine for each threshold whether available products exist.
- Determination of products that should be free but have not yet been assigned.
- Determination of free products for the next threshold.
- Determination of all previously assigned free products.
- Determination of the standard discount code (usually "aktion" in DE, "promo" in NL).
- Determination of the standard discount (percentage or amount).
Methods​
The Storefront Library returns an object named co2 that provides the following methods.
getDiscountCode​
Purpose:
Determine the currently set discount code.
Example call:
await co2.getDiscountCode();
Parameters:
None
Return value:
Discount code (string) or false (boolean). false is returned if no discount code is set.
getDiscountType​
Purpose:
Determine whether the currently set discount code is an influencer code.
Example call:
await co2.getDiscountType();
Parameters:
None
Return value:
"infl", "pub" (string) or false (boolean). Meanings:
"infl"=> Influencer code"pub"=> Not an influencer codefalse=> No code set
setDiscountCode​
Purpose:
Set a discount code. For example, after input by the user in an input field.
Example call:
await co2.setDiscountCode("aktion");
Parameters:
Discount code (string)
Return value:
true or false (boolean). Meanings:
true=> Discount code setfalse=> Discount code unknown or invalid
removeDiscountCode​
Purpose:
Remove the currently set discount code.
Example call:
await co2.removeDiscountCode();
Parameters:
None
Return value:
true (boolean). Meaning: Discount code successfully removed.
standardDiscountCode​
Purpose:
Determine the discount code of the main campaign for the current market.
Example call:
await co2.standardDiscountCode();
Parameters:
None
Return value:
Discount code (string) or false (boolean). false is returned if there is currently no main campaign.
itemDiscountFull​
Purpose:
Calculate the discount on a product variant for displaying the strikethrough price in collections and on the product page. All discount types are taken into account.
Example call:
await co2.itemDiscountFull(
123456789,
987654321,
[
"category-a",
"category-b",
"sale",
"featured",
"tag-1",
"tag-2"
],
2999,
1
);
Parameters:
The product properties required to calculate the discount are passed:
product_id(number, required),variant_id(number, required),tags(string[], required),sub_total(number, undiscounted price in cents, required),quantity(number, optional, default: 1),subscription(boolean, sale with selling plan, optional, default: false),sale_price(boolean, indicates the product is on sale —compareAtPrice > price, optional, default: false),variant_tags(string[], variant-level tags lowercase-normalized internally for variant-tag-filter matching, optional, default: [])
Return value:
Object with information for displaying the strikethrough price. Example:
{
"calc": "perc",
"discPerc": 25,
"discAmt": 0,
"discPrice": 2249,
"condPerc": 30
}
Meaning of the properties:
calc: Can be "perc" (percentage discount) or "amt" (amount discount).discPerc: Percentage if percentage discount, 0 otherwise,discAmt: Discount in cents if amount discount, 0 otherwise.discPrice: Discounted price in cents.condPerc: Maximum possible percentage discount via conditional discounts.
itemDiscount​
Purpose:
Calculate the discount on a product variant without considering conditional discounts. This means: The discPrice property is only calculated based on the standard discount groups. However, the maximum possible percentage discount is returned for conditional discounts.
Example call:
await co2.itemDiscount(
123456789,
987654321,
[
"category-a",
"category-b",
"sale",
"featured",
"tag-1",
"tag-2"
],
2999,
1
);
Parameters:
The product properties required to calculate the discount are passed:
product_id(number, required),variant_id(number, required),tags(string[], required),sub_total(number, undiscounted price in cents, required),quantity(number, optional, default: 1),subscription(boolean, sale with selling plan, optional, default: false),sale_price(boolean, indicates the product is on sale —compareAtPrice > price, optional, default: false),variant_tags(string[], variant-level tags lowercase-normalized internally for variant-tag-filter matching, optional, default: [])
Return value:
Object with information for displaying the strikethrough price. If no discount is found, then discPerc and discAmt are 0, and discPrice equals the sub_total parameter. Example:
{
"calc": "perc",
"discPerc": 20,
"discAmt": 0,
"discPrice": 2399,
"condPerc": 30
}
Meaning of the properties:
calc: Can be "perc" (percentage discount) or "amt" (amount discount).discPerc: Percentage if percentage discount, 0 otherwise,discAmt: Discount in cents if amount discount, 0 otherwise.discPrice: Discounted price in cents.condPerc: Maximum possible percentage discount via conditional discounts.
standardDiscount​
Purpose:
Calculate the discount on a product variant with the discount code of the main campaign. Used for display on the product when the user has no discount code in the cart.
Example call:
await co2.standardDiscount(
123456789,
987654321,
[
"category-a",
"category-b",
"sale",
"featured",
"tag-1",
"tag-2"
],
2999,
1
);
Parameters:
The product properties required to calculate the discount are passed:
product_id(number, required),variant_id(number, required),tags(string[], required),sub_total(number, undiscounted price in cents, required),quantity(number, optional, default: 1),subscription(boolean, sale with selling plan, optional, default: false),sale_price(boolean, indicates the product is on sale —compareAtPrice > price, optional, default: false),variant_tags(string[], variant-level tags lowercase-normalized internally for variant-tag-filter matching, optional, default: [])
Return value:
Object with information about the discount on a product when applying the standard discount. If no discount is found, then discPerc and discAmt are 0, and discPrice equals the sub_total parameter. Example:
{
"calc": "perc",
"discPerc": 20,
"discAmt": 0,
"discPrice": 2399
}
Meaning of the properties:
calc: Can be "perc" (percentage discount) or "amt" (amount discount).discPerc: Percentage if percentage discount, 0 otherwise,discAmt: Discount in cents if amount discount, 0 otherwise.discPrice: Discounted price in cents.
conditionalDiscounts​
Purpose:
Returns the conditional discounts where the condition settings or discount settings (or both) relate to the product/product variant.
Example call:
await co2.conditionalDiscounts(
123456789,
987654321,
[
"category-a",
"category-b",
"sale",
"featured"
]
);
Parameters:
product_id(number, required)variant_id(number, required)tags(string[], required)subscription(boolean, sale with selling plan, optional, default: false)sale_price(boolean, indicates the product is on sale —compareAtPrice > price, optional, default: false)variant_tags(string[], variant-level tags lowercase-normalized internally for variant-tag-filter matching, optional, default: [])
Return value:
Returns an object with the properties rule_applies and condition_applies. If no conditional discounts are found, both arrays are empty. Meaning of the arrays:
- An entry in
condition_appliesmeans that the condition matches the product. - An entry in
rule_appliesmeans that the discount is applicable to the product.
In the example, it is defined that you get a 50% discount on Product A (product ID 111111111) when you buy Product B (product ID 222222222):
{
"rule_applies": [],
"condition_applies": [
{
"quant": 1,
"count": 0,
"disc_perc": 50,
"base": "prods",
"tags": [],
"ex_tags": [],
"product_items": [
111111111
],
"product_variant_items": [],
"multi_apply": "multiply",
"cond_base": "prods",
"cond_tags": [],
"cond_ex_tags": [],
"cond_product_items": [
222222222
],
"cond_product_variant_items": [],
"cond_minimum": "quant",
"cond_quant": 1,
"cond_threshold": 0,
"cond_count": 1,
"cond_sub_total": 0,
"cond_threshold_used": 0,
"cond_text": ""
}
]
}
An entry in the rule_applies or condition_applies array has the following properties. Note that all fields beginning with "cond_" belong to the condition part, while the other fields define the discount.
quant: Number of products for which the discount applies (number). This value must be considered together with themulti_applyfield. Ifmulti_applyis "once", then the discount is only appliedquanttimes. In the example, this would mean that the customer only gets 50% on Product A once, no matter how many units of Product B they buy. Here, however, the setting is "multiply". This means: If the customer buys n units of Product B, they also get the discount on Product A n times. There is also the setting "unlimited", which is used, for example, with tiered pricing where the discount applies unlimited times.count: Number of products in the cart to which the discount has been applied (number).disc_perc: Discount in % (number).base: The products to be discounted can be defined by specifying products ("prods"), variants ("vars") or tags ("tags") (string).tags: Array of tags ifbase == "tags"(string[]).ex_tags: Array of exclusion tags ifbase == "tags"(string[]).product_items: Array of product IDs ifbase == "prods"(number[]).product_variant_items: Array of variant objects ifbase == "vars". Each object has two properties:pid(product ID) andvids(variant IDs).multi_apply: "once", "multiply" or "unlimited" (string).cond_base: Refers to the products of the condition and can be "prods", "vars" or "tags" analogous tobase(string).cond_tags: Array of tags ifcond_base == "tags"(string[]).cond_ex_tags: Array of exclusion tags ifcond_base == "tags"(string[]).cond_product_items: Array of product IDs ifcond_base == "prods"(number[]).cond_product_variant_items: Array of variant objects ifcond_base == "vars".cond_minimum: Expresses whether the condition refers to a minimum quantity ("quant") or a minimum amount ("threshold") (string).cond_quant: Minimum quantity ifcond_minimum == "quant"(number). 0 otherwise.cond_threshold: Minimum amount ifcond_minimum == "threshold"(number). 0 otherwise.cond_count: Number of products in the cart that meet the condition ifcond_minimum == "quant"(number). 0 otherwise.cond_sub_total: Sum of the discounted prices of products that meet the condition ifcond_minimum == "threshold"(number). 0 otherwise.cond_threshold_used: Ifcond_minimum == "threshold"and the rule could be applied, then the threshold that was used when applying (number).cond_text: Information text for output on the product (string).use_variant_tags: Whether the reward-side variant-tag filter is active for this rule (boolean).variant_tags: Reward-side variant-tag include list, lowercase-normalized (string[]).ex_variant_tags: Reward-side variant-tag exclude list, lowercase-normalized (string[]).cond_use_variant_tags: Whether the condition-side variant-tag filter is active for this rule (boolean).cond_variant_tags: Condition-side variant-tag include list, lowercase-normalized (string[]).cond_ex_variant_tags: Condition-side variant-tag exclude list, lowercase-normalized (string[]).
freeProductThresholds​
Purpose:
Determine the free product thresholds for display in the cart.
Example call:
await co2.freeProductThresholds();
Parameters:
None
Return value:
Array with the free product thresholds in cents (number[]). The array is empty if there are no free products. Example:
[5000, 7500, 10000]
Meaning: Thresholds in cents.
thresholdAvailabilities​
Purpose:
Determine product availability for free product thresholds. Used for display in the cart.
Example call:
await co2.thresholdAvailabilities();
await co2.thresholdAvailabilities(true);
Parameters:
getAll(boolean, optional, default: false): Controls the scope of the result. Iffalse, only thresholds already reached by the current cart sub_total are returned (the previous behavior — matches the first call above). Iftrue, all configured thresholds are returned regardless of cart state, which is useful for rendering a full milestone or progress-bar UI in the cart.
Return value:
Array of objects with free product threshold and product availability. Example:
[
{
"thresh": 5000,
"available": true
},
{
"thresh": 7500,
"available": false
},
{
"thresh": 10000,
"available": true
}
]
Meaning:
thresh: Free product threshold (number).available:trueif the threshold has at least one assigned free product (or free product variant) that is not sold out, matches thesale_modeconfigured on the rule, and passes the rule's variant-tag filter (boolean). Thesale_modefilters discounted variants:"include"(default) allows all variants,"exclude"hides discounted variants,"only"keeps only discounted variants.falsemeans no candidate variant remains — either all candidates are sold out or they are excluded bysale_modeor the variant-tag filter.
freeProductKeys​
Purpose:
Determine the item keys of free products in the cart.
Example call:
await co2.freeProductKeys();
Parameters:
None
Return value:
Array with the item keys of free products (string[]). The array is empty if there are no free products. Example:
[
"111111111:abc123def456ghi789",
"222222222:jkl012mno345pqr678",
"333333333:stu901vwx234yz567"
]
Meaning: Item keys with which free products can be identified in the cart.
unredeemedFreeProducts​
Purpose:
Determine the free products that the customer is entitled to but are not yet in the cart. Sold-out products are not returned.
Example call:
await co2.unredeemedFreeProducts();
Parameters:
None
Return value:
Array with free product objects. The array is empty if there are no free products. Example:
[
{
"threshold": 5000,
"sub_total": 7553,
"unredeemedQty": 1,
"base": "prods",
"productItems": [
"product-a",
"product-b"
],
"productVariantItems": [],
"addAutomatically": false
},
{
"threshold": 5000,
"sub_total": 7553,
"unredeemedQty": 2,
"base": "vars",
"productItems": [],
"productVariantItems": [
{
"handle": "product-c",
"variant_ids": [
111111111
]
},
{
"handle": "product-d",
"variant_ids": [
222222222,
333333333,
444444444
]
}
],
"addAutomatically": false
},
{
"threshold": 7500,
"sub_total": 7553,
"unredeemedQty": 1,
"base": "prods",
"productItems": [
"product-e"
],
"productVariantItems": [],
"addAutomatically": true
}
]
Meaning:
The objects have the following properties:
threshold: Threshold in cents (number),sub_total: Current cart total in cents (number),unredeemedQty: Quantity the customer should receive (number),base: Can be "prods" (products) or "vars" (variants).- For "prods", the
productItemsarray contains the handles of the alternative products. - For "vars", the
productVariantItemsarray is relevant. It then contains an object for each alternative product with "handle" (product handle) and "variant_ids" (allowed variant IDs). addAutomatically: Whether the product should be automatically added to the cart (boolean).
When a free-product rule has base: "prods" configured but an active variant-tag filter (use_variant_tags: true with non-empty include or exclude list), or its sale_mode is set to anything other than "include", the library surfaces the rule as base: "vars" and fills productVariantItems with { handle, variant_ids } entries restricted to the qualifying in-stock variants. This conversion is runtime-only — the rule itself remains "prods" in the merchant configuration.
unredeemedCondDiscountProducts​
Purpose:
Determine the products that the customer is currently being upsold via a conditional discount. For each candidate product, the library probes whether adding one unit to the cart would activate a conditional discount whose configuration targets specific products (rule base "prods" with sale_mode: "include"). Used for upsell display in the cart.
Example call:
await co2.unredeemedCondDiscountProducts();
Parameters:
None
Return value:
Array with the upsell product objects. The array is empty if no product currently qualifies. Example (abbreviated):
[
{
"handle": "sample-product",
"title": "Sample Product",
"image": "https://cdn.example.com/image.png",
"tags": ["tag-a", "tag-b"],
"condPerc": 30,
"variants": [
{
"id": 987654321,
"title": "Default Title",
"price": 1990,
"compareAtPrice": 2490,
"image": "https://cdn.example.com/image.png"
}
]
}
]
Meaning:
handle: Product handle (string).title: Product title (string).image: Product image URL (string).tags: Product tags (string[]).condPerc: Percentage discount that would apply if the customer adds the product to the cart (number).variants: Array of variant objects, one entry per in-stock variant. Each entry has the following properties:id: Variant ID (number).title: Variant title (string).price: Variant price in cents (number).compareAtPrice: Variant compare-at price in cents, or0if no compare-at price is set (number).image: Variant image URL (string).
Note that the variant shape here differs from the one returned by freeProductData: this method additionally exposes compareAtPrice, because conditional-discount upsells frequently depend on whether a variant is on sale.
When the rule that selected the product has an active variant-tag filter (use_variant_tags: true with a non-empty include or exclude list), the variants array is pre-filtered to the variants that actually pass the variant-tag check. Variants excluded by the filter do not appear in the result.
nextFreeProducts​
Purpose:
Determine the free products that the customer receives at the next threshold that has not yet been reached. Thresholds for which all assigned free products are sold out are skipped, so the customer is not shown a threshold where all assigned free products are sold out.
Example call:
await co2.nextFreeProducts();
Parameters:
None
Return value:
Object with information about the next threshold that has not yet been reached and for which at least one assigned free product is not sold out. If there is no such threshold (any more), false is returned. Example:
{
"threshold": 10000,
"sub_total": 7553,
"items": [
{
"threshold": 10000,
"sub_total": 7553,
"unredeemedQty": 1,
"base": "prods",
"productItems": [
"product-f"
],
"productVariantItems": [],
"addAutomatically": true
}
]
}
Meaning:
The object properties have the following meaning:
threshold: Next threshold in cents (number),sub_total: Current cart total in cents (number),items: Array with free product objects. The structure is identical to the array returned byunredeemedFreeProducts.
The same prods-to-vars runtime conversion described under unredeemedFreeProducts applies here: items returned by nextFreeProducts follow the same shape rules, including the variant-tag-filter and sale_mode cases.
freeProductData​
Purpose:
Determine the data of all products that are defined as free products for the current campaign. For each product, all available variants are returned.
Example call:
await co2.freeProductData();
Parameters:
None
Return value:
Array with free products. The array is empty if there are no free products. Example (abbreviated):
[
{
"id": 123456789,
"title": "Sample Product",
"handle": "sample-product",
"productType": "Product Type",
"image": "https://cdn.example.com/image.png",
"variants": [
{
"id": 987654321,
"title": "Default Title",
"price": 1990,
"image": "https://cdn.example.com/image.png"
}
]
}
]
Meaning:
The product objects have the following properties:
id: Product ID (number)title: Product title (string)handle: Product handle (string)productType: Product type (string)image: URL of featured image (string)variants: Array with all available variant objects. Properties are:id: Variant ID (number)title: Variant title (string)price: Variant price in cents (number)image: URL of variant image (string)
Integration​
Storefront API Access Token​
For the library to work, it needs to read and set discount codes, read product tags of cart items, and look up properties of free products. This requires a Storefront API access token for product data. Cart state and discount-code changes are handled through Shopify's cart endpoints.
The token is generated directly from the app. Token generation is available on the Ultimate plan; see the Datora app's pricing page for details.
- In the app menu, click Settings.
- In the Storefront Access Token section, click Generate Token.
- An access token is created and can be copied.
Next, open the theme editor:
Online Store > Themes > Customize
In the left sidebar, click the third icon (App embeds). The following two toggles must be enabled:
- Datora | Multi Discounts
- Storefront API Client
Click the small arrow next to Storefront API Client to reveal the Storefront API access token field. Paste the token here and save.
DATORA Sales Channel​
After generating the Storefront API token and entering it in the theme, assign the DATORA sales channel to your products. This is required when using the Storefront Library for free products or conditional discounts.
In the Shopify admin: open the product list and select all products, click the "N selected" label, then Select all in this store > ... > Include in sales channels > DATORA > Include products.
Events​
cart.update​
The Storefront Library maintains its own cart state and listens for the cart.update event. Whenever the cart changes, this event must be dispatched; the library then refreshes its state. The event is dispatched as follows:
window.dispatchEvent(new CustomEvent('cart.update'));
The right place to hook this in depends on the theme. The following snippet is a Dawn-specific example, not a theme-agnostic API: in the Dawn theme, the following code can be appended to the end of global.js:
subscribe(PUB_SUB_EVENTS.cartUpdate, (event) => {
window.dispatchEvent(new CustomEvent('cart.update', {
detail: { cart: event.cartData }
}));
});
This example also passes the cart in the event detail. That is not required, but it makes things faster because otherwise the library has to re-fetch the cart itself. Pass it whenever it is available.
co2.afterCartAttributeChange​
When a user follows a code link, the Storefront Library sets a cart attribute and dispatches the window event co2.afterCartAttributeChange. To apply the new code immediately, the cart must be refreshed; in the Dawn theme, that means publishing a cartUpdate event.
In the Dawn theme (again as a theme-specific example), the following code can also be appended to global.js:
window.addEventListener('co2.afterCartAttributeChange', () => {
publish(PUB_SUB_EVENTS.cartUpdate, { source: 'global', cartData: null });
});
This way, after the cart attribute is set, a cart update is triggered automatically, and the new discount code takes effect immediately.
co2.cart.initialized​
The Storefront Library dispatches the window event co2.cart.initialized as soon as it has finished initializing. This fires both on first page load and after every cart change — that is, every time the library has re-initialized its internal state.
This makes it the recommended hook for refreshing strikethrough prices: listen for co2.cart.initialized whenever you want to recalculate prices after the page loads or the cart changes. You do not need to build your own timing logic (for example around DOMContentLoaded) to work out when the library is ready to be queried — the event signals exactly that moment, and the library's internal state is guaranteed to be current.
Example:
window.addEventListener('co2.cart.initialized', async () => {
const discount = await co2.itemDiscount(
productId, variantId, tags, subTotal, quantity, false, false
);
// Update strikethrough price ...
});
Sale Price Logic​
- A variant is treated as a sale-price variant when
compareAtPrice > price. - The Storefront Library does not infer this for the wrapper calls; the theme caller passes
sale_price(boolean) intoitemDiscount,itemDiscountFull,standardDiscount, andconditionalDiscounts. - A discount rule's
sale_modecontrols how sale-price variants are treated:"include"(default) allows all variants,"exclude"hides discounted variants,"only"keeps only discounted variants. - This
sale_modefiltering is used when the Storefront Library checks whether a discount rule matches a variant. It is visible in free-product availability and preview (thresholdAvailabilities,unredeemedFreeProducts,nextFreeProducts) and in conditional-discount checks.unredeemedCondDiscountProductsonly returns upsell products forbase: "prods"rules withsale_mode: "include", and usescompareAtPrice > priceinternally when probing candidate variants. - Existing examples stay unchanged:
sale_priceis optional and defaults tofalse, so callers that omit it keep working.
Setup for Variant-Tag filtering​
Defining the co2.variant_tags metafield​
Variant-level tags are stored in a Shopify metafield. Define it once per shop:
- In the Shopify admin, open Settings → Custom data → Variants → Add definition.
- Set the namespace and key to
co2andvariant_tags. - Choose type List of single line text (
jsonalso works; the library parses either form). - Under Access, enable Storefront API: read. Without this, the storefront preview falls back to "no variant tags"; cart and checkout enforcement still works because the Shopify Function reads metafields server-side.
Assigning tags to variants​
Open a product's variant and fill the co2.variant_tags field with the tags relevant for filtering (for example, ["exclusive", "vip_member"]). Tags are normalized to lowercase before comparison.
Use ASCII slug-style values such as exclusive, vip_member, or size-xl. Case-insensitive matching is guaranteed for ASCII tags, so avoid umlauts or locale-specific casing.
Configuring a Conditional Discount rule​
In the Datora app admin, create or edit a Conditional Discount rule and enable Filter by Variant-Tags. Specify the include and/or exclude lists on either the reward side or the condition side, as required.
Theme caller update (optional but recommended)​
For accurate storefront product-grid preview (strikethrough prices, badges) on variant-tag-filtered products, update the theme to pass the variant's tags as the final positional argument to the relevant Storefront Library wrapper. The argument must be a plain string[]. Shopify exposes list-of-text metafield values as a JSON-serialized string on the storefront, so parse the value first:
let variantTags = [];
const rawVT = variant.metafields?.co2?.variant_tags?.value;
if (rawVT) {
try { variantTags = JSON.parse(rawVT); } catch { variantTags = []; }
if (!Array.isArray(variantTags)) variantTags = [];
}
await co2.itemDiscountFull(
product.id,
variant.id,
product.tags,
variant.price,
1,
false, // subscription
variant.compareAtPrice > variant.price, // sale_price
variantTags // variant_tags
);
Without this update, the library falls back to an empty variant-tag set for preview computations. Cart and checkout enforcement is unaffected because the Shopify Function reads variant metafields directly server-side.