Metafields (lc_upsell)
Reference for the lc_upsell metafield namespace, its minified dsm/csm structure, how the product-discount Function reads it, and the full list of app metafield namespaces and keys.
The Llama Upsell app stores product-discount configuration in a Shopify metafield under the lc_upsell namespace. This page documents the exact namespace and key, the minified value structure, the sync behavior that writes it, and how the product-discount Function extension reads it at checkout.
The lc_upsell metafield lives on Shopify automatic discount nodes, not on the shop or on individual products. It is read by the product-discount Shopify Function (WASM) at checkout, not from Liquid. Other Llama features use different namespaces and keys — see Other namespaces.
Namespace and key
lc_upsell holds a single key. Both values are defined in code and are exact — do not guess at variants.
| Field | Value |
|---|---|
| Namespace | lc_upsell |
| Key | configuration |
| Owner resource | Shopify automatic discount node |
| Read by | Product Discount Function extension |
| Value format | IAutomaticDiscountMetafieldMinifiedValue (JSON) |
These constants come from the shared EDiscount enum:
export enum EDiscount {
NAMESPACE = 'lc_upsell',
KEY = 'configuration',
}Why the value is minified
Shopify limits a function-input metafield value to 10,000 characters. One lc_upsell metafield can contain every condition and variant across all of a shop's discount configurations, so it is the value most likely to approach that limit.
To stay within it, the app minifies the JSON: verbose property names are replaced with one- to two-character keys before the value is written. The server enforces a safety margin of 9,990 characters (the ALLOWED_CHARACTERS_LIMIT in the codebase) and splits the data across multiple discount nodes when a single value would exceed it. See Sync and splitting behavior.
Value structure
The stored value is a JSON object with two top-level maps: dsm (discounts map) and csm (conditions map).
Discounts map (dsm)
dsm maps a discount ID to that discount's display name, type, and amount.
| Minified key | Full name | Description |
|---|---|---|
dsm | discountsMap | Top-level map keyed by discount ID |
n | name | Discount display name |
t | type | percentage or fixed_amount |
a | amount | Discount amount value |
Conditions map (csm)
csm maps a condition ID to its limits and a per-variant configuration. Each variant entry references an entry in dsm by ID.
| Minified key | Full name | Description |
|---|---|---|
csm | conditionsMap | Top-level map keyed by condition ID |
mpv | maxProductsVariety | Maximum number of unique products allowed |
vs | variants | Per-variant configuration map, keyed by variant ID |
aq | allowedQuantity | Maximum quantity for that variant |
dId | discountId | Reference to a dsm entry |
TypeScript shape
The full minified type from discount.types.ts:
export interface IAutomaticDiscountMetafieldMinifiedValue {
/** Discounts map */
dsm: {
[discountId: string]: {
/** Name of the discount */
n: string;
/** Discount type: fixed_amount | percentage */
t: EDiscountType;
/** Discount amount */
a: number;
};
};
/** Conditions map */
csm: {
[conditionId: string]: {
/** Max product variety */
mpv: number;
/** Variants map */
vs: {
[variantId: string]: {
/** Allowed quantity */
aq: number;
/** Assigned discount ID */
dId: string;
};
};
};
};
}Example value
A single metafield value, shown unminified first for clarity, then in the exact shape stored under lc_upsell:configuration.
{
"dsm": {
"abc123": {
"n": "10% OFF",
"t": "percentage",
"a": 10
}
},
"csm": {
"cond_001": {
"mpv": 3,
"vs": {
"44012345678": {
"aq": 2,
"dId": "abc123"
},
"44012345679": {
"aq": 1,
"dId": "abc123"
}
}
}
}
}{
"discountsMap": {
"abc123": {
"name": "10% OFF",
"type": "percentage",
"amount": 10
}
},
"conditionsMap": {
"cond_001": {
"maxProductsVariety": 3,
"variants": {
"44012345678": {
"allowedQuantity": 2,
"discountId": "abc123"
},
"44012345679": {
"allowedQuantity": 1,
"discountId": "abc123"
}
}
}
}
}Reading the metafield
The app reads lc_upsell:configuration from the discount node via the Admin API. Two shared queries select it by namespace and key.
Fetch all discounts
GET_ALL_DISCOUNTS_QUERY lists automatic product discounts and pulls each node's metafield:
{
discounts: discountNodes(
first: 30,
query: "title:*llama upsells* AND discount_class:PRODUCT AND method:automatic"
) {
nodes {
metafield(namespace: "lc_upsell", key: "configuration") {
id
value
}
discount {
... on DiscountAutomaticApp {
discountId
startsAt
title
status
endsAt
}
}
}
}
}Fetch a single discount
GET_DISCOUNT_QUERY selects the same metafield for one node by ID:
query GetDiscount($id: ID!) {
discountNode(id: $id) {
id
metafield(namespace: "lc_upsell", key: "configuration") {
id
value
}
discount {
__typename
... on DiscountAutomaticApp {
title
status
discountClass
combinesWith {
orderDiscounts
productDiscounts
shippingDiscounts
}
startsAt
endsAt
}
}
}
}Because the value is minified, read it through these shared queries and parse with the documented dsm/csm shape. Reading it directly without decoding the short keys will not give you usable discount data.
How the Function reads it at checkout
At checkout, the product-discount Function (WASM) reads the metafield from its GraphQL input and parses it. It never calls the backend — all data comes from the metafield, which keeps checkout fast and reliable.
const metafieldMinifiedValue = JSON.parse(
input.discountNode?.metafield?.value || '{}'
);
const { csm, dsm } = metafieldMinifiedValue;The Function then:
Read the maps
Parse dsm and csm from the metafield value, falling back to an empty object when the value is missing.
Match cart lines to conditions
Iterate cart lines looking for the _lcu_condition_id cart-line attribute, and look up that condition in csm by its ID.
Resolve the variant and discount
Find the variant in csm[conditionId].vs[variantId], then resolve the discount from dsm[variant.dId].
Enforce limits and return targets
Apply mpv (max products variety) and aq (allowed quantity), then return the discount targets.
Sync and splitting behavior
The server writes lc_upsell:configuration whenever a discount is created or updated, through createDiscount and updateDiscount in discounts.service.ts. Both persist the value via the generic syncAppMetafield method. The value is minified before each write.
When the minified value would exceed the 9,990-character safety limit, the server automatically splits the data across multiple Shopify automatic discount nodes:
- The
dsm(discounts map) is duplicated across every node, because the Function on each node needs to know about all discounts. - The
csm(conditions map) is partitioned — each condition is written to exactly one node. - Unused discount nodes are cleaned up after the data is redistributed.
- Each generated node is titled
Llama Upsells - N - DO NOT MODIFY.
Do not rename, edit, or delete discount nodes titled Llama Upsells - N - DO NOT MODIFY. They are managed by the sync process, and changing them can break discount application at checkout.
Write triggers
Metafield sync runs on every create, update, and delete for the relevant feature. For product discounts specifically, the trigger is:
// On discount create/update — createDiscount / updateDiscount
// persist the minified value through the shared metafield sync.
await this.syncAppMetafield(session, minifiedValue);Other namespaces
lc_upsell is dedicated to product discounts. Other Llama features store their data under their own namespaces and keys — these are listed here only so you do not confuse them with lc_upsell.
| Feature | Owner resource | Namespace | Key |
|---|---|---|---|
| Product Discounts | Discount node | lc_upsell | configuration |
| Active Campaigns | Shop | $app:campaigns | active |
| Restrictions Config | Validation | $app:restrictions | active_configs |
| Products Limiter | Shop | $app:limiters | active |
| Tier Discount Config | Discount function | $app:tier_discount | configuration |
| Shipping Discount Config | Discount function | $app:shipping_discount | configuration |
Namespaces prefixed with $app: are app-reserved and scoped to the installing app.
Full metafield reference (all namespaces/keys)
The feature table above covers the metafields you are most likely to encounter, and is a subset of this list. Below is the complete list of every metafield namespace/key the app uses, along with the constant each pair is defined as. Use them exactly as written — a wrong namespace or key means your custom code silently reads nothing.
| Constant | Namespace | Key |
|---|---|---|
EShopActiveRestrictionsMetafield | $app:restrictions | active |
EShopActiveProductsLimiterMetafield | $app:limiters | active |
EShopActiveCampaignsMetafield | $app:campaigns | active |
EShopCampaignsTranslatableMetafield | llama-translatable-campaigns | active |
EShopLlamaCartTranslatableMetafield | llama-translatable-cart | content |
ECartValidationActiveCampaignsMetafield | $app:params | active_campaigns |
ECartValidationVariablesMetafield | $app:params | active_restrictions |
ECartValidationRestrictionsMetafield | $app:restrictions | active_configs |
EShippingDiscountMetafield | $app:shipping_discount | configuration |
EShippingDiscountVariablesMetafield | $app:shipping_discount | variables |
ETierDiscountMetafield | $app:tier_discount | configuration |
ETierDiscountVariablesMetafield | $app:tier_discount | variables |
ECustomizerMetafield | campaigns | customizations |
EAppActiveCampaignsMetafield | campaigns | active |
EAppPrePurchaseCampaignsDetailsMetafield | pre_purchase_campaigns | details |
EAppDiscountsMetafield | discounts | default |
EAppAdminSettingsMetafield | settings | global_styles |
EAppGlobalMetafield | settings | global |
EAppSubscriptionMetafield | subscription | status |
EShopGlobalTranslationMetafield | global_translation | settings |
EShopCheckoutGlobalSettings | checkout | global-settings |
EAppBlocksDeprecationMetafield | deprecation | customizer_2024_02 |
EAppStorefrontAcessMetafield | settings | storefrontAccessToken |
SMART_CART_METAFIELD | smart-cart | customization |
The product-discount pair lc_upsell / configuration documented at the top of this page comes from the separate EDiscount enum and is not repeated in this table. The Llama Cart customization metafield (smart-cart namespace) stores one key per cart version: customization for version A, customization-b for version B, and customization-c for version C. The app also uses one metaobject (not a metafield): EShopCurrencyRateMetaObject — type $app:lcu_money, handle lcu_currency_rate, key base.
Related
Global JavaScript API
Reference for the namespaced runtime object the Upsell theme extension attaches to window, its bootstrap helpers, and the onProductAddToCart callback.
Llama Cart for developers
How the Llama Cart smart cart loads, stays in sync via a fetch override, opens programmatically, and runs in preview mode.
