Skip to content

Items Variables

CoreCategory12 Variables

Your dataLayer pushes product data in one shape. GA4 wants it one way, Meta another, Google Ads a third, TikTok a fourth. Items is the adaptation layer — 12 sandbox-safe variable templates that reshape any array of product objects into whatever the next tag actually needs. No Custom JavaScript, no .forEach() loops, no container rot.

12 variables100% sandbox-safeWeb + ServerMIT licensed

Every ecommerce site pushes product data into the dataLayer in one shape — whatever shape the backend happens to produce. GA4, Google Ads, Meta, TikTok, Pinterest, and every other ad platform want it in a different shape, with different key names, different types, different structures, different required fields. That mismatch is the single most common cause of broken ecommerce tracking.

The Items category is 12 variables that do one job: reshape arrays of product objects. Compute totals from them, extract flat lists from them, rename their keys, drop fields, fill defaults, filter by condition, deduplicate them. One input, many outputs. One source of truth, as many destination shapes as you need.

Here’s what each platform actually expects on an add_to_cart event — four platforms, zero overlap in key names:

GA4item_id
item_name
price
quantity
Meta Pixelcontent_ids
contents
content_type
value
Google Adsid
google_business_vertical
ecomm_prodid
ecomm_totalvalue
TikTokcontent_id
content_type
contents[]
value

The job of your GTM container isn’t to store this data — your dataLayer does that. The job is to adapt it on the fly, per tag, per destination. That’s what these variables do.

Your input probably looks like one of these

Section titled “Your input probably looks like one of these”

The Items variables don’t care where your product data comes from. They operate on any array of objects with named properties. In the wild, that array shows up in three dominant shapes — plus a long tail of platform-specific variants.

Most common

GA4 native

items: [
{
item_id: “SKU_001”,
item_name: “Sweater”,
price: 89,
quantity: 2
}
]
Legacy

Enhanced Ecommerce

products: [
{
id: “SKU_001”,
name: “Sweater”,
price: 89,
quantity: 2
}
]
Platform-specific

Custom (Shopify, etc.)

cart_products: [
{
sku: “SKU_001”,
title: “Sweater”,
unit_price: 89,
qty: 2
}
]

All three work. The same variables — sumProductOfProperties, pluckProperty, ⚡ ITEMS › Transformer — run against any of them. Only the property names you reference change. This is the point of operating at the structural level rather than the schema level: the adapter doesn’t care about vocabulary, only about shape.

Where examples in this guide use GA4 keys (item_id, price, quantity), substitute your own. The recipes don’t change.

Twelve variables, grouped by the job they do. Each links to its reference page with full config details.

The value parameter of every GA4 ecommerce event — add_to_cart, begin_checkout, purchase — expects the total monetary value of the transaction. Google’s guidance is explicit: value should reflect price multiplied by quantity, summed across all items. Most implementations either hard-code it at the dataLayer push (fragile when the dataLayer is built from CMS fragments) or reach for a Custom JavaScript variable.

Recipe — Cart value from ecommerce.items
Variable: sumProductOfProperties · Direct mode
FieldValue
Input Array{{DL - ecommerce.items}}
Property 1price
Property 2quantity
Output Function{{Apply - toFixed(2)}}
178.00 for [{price: 89, quantity: 2}]

Non-numeric values are ignored silently, so a missing or malformed price won’t throw — it just doesn’t contribute. Chain itemAssignDefaults upstream if you want explicit defaults instead of silent skips.

Common mistake. Using sumProperty with price looks right but ignores quantity entirely. A cart of one $50 item with quantity 3 returns $50, not $150. Use sumProductOfProperties for any real cart value.

A large share of ad-network integrations come down to one operation: give me an array of the product IDs in this event. Meta wants content_ids. Google Ads wants ecomm_prodid. TikTok wants content_id. Pinterest wants product_ids. Same data, four different keys.

Recipe — Flat array of product IDs
Variable: pluckProperty · Direct mode
FieldValue
Input Array{{DL - ecommerce.items}}
Propertyitem_id
[“SKU_001”, “SKU_002”, “SKU_003”]

When IDs need to be strings (and when they need to be numbers)

Section titled “When IDs need to be strings (and when they need to be numbers)”

Meta accepts both numeric and string IDs but warns that inconsistent types between pixel events and the catalog feed cause match failures. Google Ads dynamic remarketing is stricter: the id must match the product feed’s format exactly, which is usually a string. If your dataLayer sometimes pushes numeric item_id values, normalize them before plucking — chain itemMapProperty with toString upstream.

The Meta Pixel’s AddToCart, ViewContent, and Purchase events accept a contents parameter — an array of { id, quantity, item_price } objects. This is the format Meta uses for catalog matching in Advantage+ campaigns, so getting it right materially affects ad delivery.

Three things happen at once: item_id becomes id, price becomes item_price, and quantity must be an integer. One variable handles all three.

Recipe — GA4 items → Meta contents
Variable: ⚡ ITEMS › Transformer · Direct mode
SourceOutput keyType
item_ididString
priceitem_priceNumber
quantityquantityInteger
[{id: “SKU_001”, item_price: 89, quantity: 2}]

Pair this with pluckProperty on item_id for the sibling content_ids parameter, and sumProductOfProperties for value. One dataLayer push, three variables, complete Meta payload.

TikTok’s Pixel and Events API accept a contents array structurally similar to Meta’s — { content_id, content_type, content_name, price, quantity } — with one meaningful difference: content_type must be present on every item, not just the event. It’s either "product" or "product_group".

Recipe — GA4 items → TikTok contents
Step 1 — Rename and retype via ⚡ ITEMS › Transformer (Apply mode)
SourceOutput keyType
item_idcontent_idString
item_namecontent_nameString
pricepriceNumber
quantityquantityInteger
feeds into itemSet with Property: content_type, Value: “product”

This two-step pattern — Transformer for rename + type, itemSet for static injection — is the canonical shape for any network that demands a fixed per-item field your dataLayer doesn’t natively carry.

Prepare items for Google Ads dynamic remarketing

Section titled “Prepare items for Google Ads dynamic remarketing”

Google Ads dynamic remarketing is the strictest major platform because it joins to your Merchant Center product feed. The id on each item must match the id in your feed exactly, character-for-character, or match rate collapses and dynamic creatives show the wrong products. The Google Ads tag also expects google_business_vertical on every item — typically "retail", but "hotel", "flights", "education" per business type.

Recipe — GA4 items → Google Ads remarketing items
Step 1 — Rename & retype via ⚡ ITEMS › Transformer (Apply mode)
SourceOutput keyType
item_ididString
pricepriceNumber
quantityquantityInteger
then itemSet: Property: google_business_vertical, Value: “retail”

The legacy ecomm_prodid / ecomm_totalvalue / ecomm_pagetype parameters used by older Google Ads remarketing tags are derived from the same items array:

Feed consistency is non-negotiable. If your Merchant Center feed uses "shopify_US_1234_567" as id, sending "1234" from your dataLayer shows up as 0% match rate in the Data Manager. Transformer’s String type coercion gives you format stability; itemMapProperty with a format template gives you composed IDs when the feed uses them.

The Pinterest Tag expects a product_ids flat string array and a line_items array of { product_id, product_name, product_price, product_quantity, product_category, product_brand } objects. Every key prefixed with product_ — a convention unique to Pinterest.

Recipe — GA4 items → Pinterest line_items
Variable: ⚡ ITEMS › Transformer · Direct mode
SourceOutput keyType
item_idproduct_idString
item_nameproduct_nameString
priceproduct_priceNumber
quantityproduct_quantityInteger
item_categoryproduct_categoryString
item_brandproduct_brandString

For the sibling product_ids parameter, a separate pluckProperty on item_id is cleaner than trying to derive it from the transformed array.

Legacy containers still push Enhanced Ecommerce-style data — ecommerce.purchase.products or ecommerce.add.products with fields like id, name, category, brand, variant, price, quantity. Converting to GA4 shape is the most common first task when modernizing a tracking stack, and the transformation is mechanical: rename, retype, fix the wrapper path.

Recipe — EEC products → GA4 items
Variable: ⚡ ITEMS › Transformer · Direct mode
SourceOutput keyType
iditem_idString
nameitem_nameString
categoryitem_categoryString
branditem_brandString
variantitem_variantString
pricepriceNumber
quantityquantityInteger
Keep unmapped properties: ON. Custom fields survive.

Running this in every GA4 tag that still reads from the legacy dataLayer buys migration time without requiring engineering to redesign the dataLayer synchronously.

Two classic scenarios need item-level filtering. The first is category-specific pixels — a performance campaign that only tracks apparel, or a category exclusion pixel that needs only “Gifts.” The second is financial: excluding items below a threshold, dropping “shipping” or “fees” pseudo-items that some dataLayers inject as line items.

Recipe — Keep only items in a given category
Variable: itemFilterByProperty · Direct or Apply mode
FieldValue
Input Array{{DL - ecommerce.items}}
Propertyitem_category
Modeequals
ValueApparel

Chain the filtered array into sumProductOfProperties (Apply → Direct) to compute category-specific revenue for reporting, or use it as the items source of a dedicated pixel tag with a filtered scope. The six filter modes — equals, not equals, contains, regex, truthy, falsy — cover virtually every dataLayer variation you’ll encounter.

Some ecommerce platforms push duplicate item objects when a user rapidly clicks “add to cart” on the same SKU, or when a single-page-app re-renders state. Purchase events end up with item_id: "SKU_001" appearing three times — once with quantity: 1, once with quantity: 2, once again with quantity: 1. Revenue on that array is wrong.

Recipe — Deduplicate items by item_id
Variable: itemUniqByProperty · Apply mode
FieldValue
Input Array{{DL - ecommerce.items}}
Propertyitem_id

First occurrence wins by default. If your dataLayer pushes the correct total quantity on the last occurrence, reverse the array first with arrReverse before deduping — keeps only the last push per SKU.

Real-world dataLayers are inconsistent. Quantity missing on some single-item events. Price zero for free samples. item_brand present only for some product types. Ad networks don’t forgive missing fields gracefully — Meta reports empty content_ids, Google Ads shows 0% match, Pinterest silently drops the event.

The defensive pattern is one upstream normalization variable that guarantees a known shape before anything downstream touches the array.

Recipe — Fill missing quantities and prices
Variable: itemAssignDefaults · Apply mode
KeyDefault
quantity1
price0
item_brand”Unknown”
Only missing/falsy values are replaced. Explicit zeroes stay intact.

Place this at the start of every item-processing chain. Costs nothing when the dataLayer is already clean, saves the entire pipeline when it isn’t.

Ad networks routinely reject item payloads with unrecognized keys, or — worse — silently accept them and use them against their matching logic. GA4-native fields like item_list_id, index, affiliation, item_list_name, coupon are meaningful to GA4 and noise to everyone else.

Recipe — Remove GA4-only fields before ad networks
Variable: itemOmit · Apply mode
FieldValue
Input Array{{DL - ecommerce.items}}
Properties to omititem_list_id, item_list_name, index, affiliation, coupon

In practice most pipelines use Transformer with Keep unmapped: OFF instead, which achieves the same result by allowlist. Use itemOmit when you want to keep the original field set minus a few — a denylist pattern — rather than enumerate everything to keep.

Sometimes the transformation is surgical: uppercase every item_brand for a case-sensitive feed match, strip leading zeroes from item_id, prefix every SKU with a market code, or force price to always be a number even when the dataLayer pushes strings.

Recipe — Force item_id to string across all items
Variable: itemMapProperty · Apply mode
FieldValue
Input Array{{DL - ecommerce.items}}
Propertyitem_id
Map Function{{Apply - toString}}

When two or more properties need transforming, Transformer is almost always the better answer: one variable, one mapping table, clear intent. itemMapProperty is the right tool when the transformation is one property, one function, no renaming.

The framework’s real power shows up when variables compose. Because every Items variable supports Apply mode, the output of one becomes the input of the next — no intermediate GTM variables, no Custom JavaScript glue. Three patterns come up over and over:

Fill missing quantities and prices, then compute. Works even when the dataLayer is unreliable.

itemAssignDefaultsApplysumProductOfPropertiesDirect

Revenue for just one product line — useful for category-level goal tracking in GA4 or category-specific conversion values in Google Ads.

itemFilterByPropertyApplysumProductOfPropertiesDirect

Remove duplicate items before reshaping. The Transformer output goes straight into the Meta tag’s contents field; a parallel pluckProperty feeds content_ids.

itemUniqByPropertyApply⚡ ITEMS › TransformerDirect

These chains are why you’d use a composable framework instead of one-off Custom JavaScript. Every chain is readable end-to-end from the variable list; nobody has to open code to understand what the container is doing.

Every competing tutorial on calculating cart value, sending items to Meta, or building Google Ads remarketing arrays reaches for the same pattern:

function() {
var contents = [];
{{DLV - ecommerce.items}}.forEach(function(el) {
contents.push({
id: el.item_id,
quantity: el.quantity
});
});
return contents;
}

It works. It also creates three problems that compound across a container:

  • Audit opacity. The next analyst has to read every Custom JavaScript variable line by line. Across 20 ad networks and 8 ecommerce events, no one fully understands the container.
  • Sandbox mismatch in sGTM. Server-side Google Tag Manager runs in a far stricter sandbox; many ad-hoc Custom JavaScript patterns from web containers simply don’t transfer.
  • Duplication. “Extract item_id as a string array” is the same transformation on every site. No reason to rewrite it each time.

Named templates like pluckProperty and sumProductOfProperties carry intent in their name. An audit becomes a scan, not an investigation. The container stays legible when the analyst who built it is gone.

GoalVariable
Total cart value (price × quantity)sumProductOfProperties
Sum one property (no quantity factor)sumProperty
Sum nested property pathsumPropertyNested
Flat array of one property (IDs, names)pluckProperty
Rename + retype + default multiple fields⚡ ITEMS › Transformer
Transform one property across all itemsitemMapProperty
Filter items by a property conditionitemFilterByProperty
Remove duplicate itemsitemUniqByProperty
Inject a static property onto every itemitemSet
Fill missing properties with defaultsitemAssignDefaults
Remove specific properties from every itemitemOmit
Split a delimited string property into an arrayitemSplitProperty

Can I calculate cart value in GTM without Custom JavaScript?

Yes. sumProductOfProperties multiplies two numeric properties (typically price and quantity) across every item in the array and returns the sum. It runs inside GTM’s sandboxed JavaScript environment — no Custom JavaScript needed.

What’s the difference between sumProperty and sumProductOfProperties?

sumProperty adds one property across items: price + price + price. sumProductOfProperties multiplies two properties per item then sums the results: (price × quantity), summed. For any real cart value, use sumProductOfPropertiessumProperty ignores quantity.

Do these variables work with non-GA4 dataLayers?

Yes. They operate on any array of objects with named properties. Whether your dataLayer uses GA4 shape, legacy Enhanced Ecommerce, or a custom platform-specific shape, the same variables apply — only the property names change.

Do these variables work in server-side GTM?

Each variable is marked for Web, Server, or both on its individual page. The Items category is primarily web-container oriented, but most transformation variables have server equivalents for outbound payload shaping.

Are these templates sandbox-safe?

Yes. All templates run inside GTM’s sandboxed JavaScript environment using only the permissioned APIs exposed to custom templates. They pass GTM’s built-in permission checks without modifications.

How do I chain multiple Items variables together?

Most Items variables have an Apply mode that returns a reusable function rather than a final value. You chain these templates together by feeding that function into another variable’s Input Function field (to transform the array before processing) or into its Output Function field (to transform the result after processing). For example, using itemAssignDefaults (Apply) in the Input Function of sumProductOfProperties (Direct) fills missing quantities before the total is summed.

Why not just use a Custom JavaScript variable?

Custom JavaScript works but creates audit opacity — the next analyst has to read code to understand intent. Named templates like pluckProperty carry meaning in the name. Containers become scannable instead of investigative.