Start with the foundations › Variables: Props, eVars, and Events
The Products String
Default and reserved variables flagged this one as the exception. Most reserved variables do one small job, but the products variable runs your entire ecommerce funnel through a single string: what was viewed, added, checked out, and bought, how many, for how much, and even why. It is also where several earlier threads meet: the hit-scoping of props, the attribution behavior of eVars, and the reserved commerce events all converge inside it. Get it right and the whole commerce side of Adobe Analytics works. Get a single delimiter wrong and revenue quietly breaks with no error message. It is the most powerful and the most punishing variable in the product, which is exactly why it earns its own section.
One string, one order, many products
The plural name fools people. You set one s.products string per hit. On a purchase, that single string represents the whole order, and it can carry many products inside it, separated by commas. So "products" means the basket, not one item.
Anatomy, and the three delimiters that decide everything
The string is positional: meaning comes from which slot a value sits in, not from any label. Six fields per product, in this fixed order:
Three delimiters do all the structural work, and confusing them is the number-one cause of broken commerce data:
| Delimiter | Separates | Example |
|---|---|---|
Semicolon ; | the six fields within one product | Apparel;Blue Shirt;2 |
Comma , | one product from the next | Shirt;1;29.99,Socks;3;26.91 |
Pipe | | multiple events, or multiple merchandising eVars, inside a field | event1=1|event2=5 |
What becomes which KPI
Three pieces of the string turn directly into the metrics your business lives by:
Three fields of the string become three of the numbers everyone quotes.
Field in s.products | Becomes |
|---|---|
the purchase event | Orders |
| the price field | Revenue |
| the quantity field | Units |
The catch most people hit at least once: the purchase event must be explicitly fired in s.events for Orders to register, and quantity and price are only counted on the hit where purchase fires. A products string without the purchase event on the order page means revenue silently goes nowhere.
It has no memory: set it on every funnel hit
Here's the trait that surprises people: the products variable does not persist. It's hit-scoped, like a prop (the pencil behavior from props), the value vanishes after the hit it rode in on, even though it carries events and merchandising eVars that behave with attribution like eVars. So it straddles both worlds: a non-persistent carrier of persistent data. Practically, that means you must re-send s.products on every funnel hit where product context matters, alongside the reserved commerce events from default and reserved variables (prodView, scAdd, scCheckout, purchase).
Product-level events (and the caveat that bites)
Slot 5 lets you attribute a metric to a single product, give one product an event another doesn't get. Three rules govern it:
- It must also appear in
s.events. Otherwise Adobe ignores it entirely. The events variable is the gatekeeper; the products string only assigns the event to a product. - Don't set the same numeric event in both places. If a numeric value is set in both
s.eventsand the products string, the events value wins. - Usually numeric or currency, occasionally counter. They typically carry a per-product number (product revenue, shipping), but a counter is allowed, the same event types from events.
// event1 must be in BOTH places, or it won't count s.events = "purchase,event1"; s.products = "Apparel;Blue Shirt;2;59.98;event1=5;eVar1=blue"; // Two products, an event credited only to the second s.events = "event1"; s.products = "Apparel;Shirt;1;29.99,Footwear;Socks;3;26.91;event1=3";
Merchandising eVars: bound to a product, not the visitor
A normal eVar attaches its value to the visitor. A merchandising eVar, first mentioned back in the eVar section, binds it to a specific product instead, so you can answer "revenue by the on-site search term that surfaced this product." Two syntaxes:
- Product syntax (recommended): set the eVar right in slot 6 of the products string. Use this when you know the value at the moment the product is set.
- Conversion variable syntax: set the eVar separately and bind it via a binding event, for when the value isn't known at product time.
The conversion-syntax trap: the eVar value, the products variable, and the binding event must all line up on the right hit, or the instance lands in "None / Unspecified."
The mindset shift: don't treat a merchandising eVar like a normal one. It doesn't simply persist to the visitor, its association is product-scoped and deliberate, so you repopulate or rebind it as products change.
Order-level vs product-level: put data where it belongs
A clean design separates the two. Mixing them up is one of the most common implementation design mistakes, and it's painful to unwind later.
Props cap at 100 bytes and eVars at 255, but the products string can hold up to 64k bytes, effectively unlimited for any real basket. When a hit's request grows long (beyond about 2047 bytes), AppMeasurement automatically switches the beacon from a GET to a POST request, and POST data is not truncated regardless of length. So large multi-product orders are handled fine, you just won't see the whole string in the request URL when you're debugging a GET.
Build it once, the right way
Don't hand-concatenate the products string inside each Launch rule, that's how delimiter bugs breed. The durable pattern: have developers expose product data as parallel arrays in the data layer with one entry per product, even when there's only one, then write a single formatting function, placed just below doPlugins so it's globally available, that assembles the string correctly. Call that one function from every relevant Tags rule, cart add, checkout, purchase. One function, one source of truth, no per-rule string-building.
// Data layer exposes parallel arrays (works for 1 or many products)
// digitalData.products.name[], .category[], .qty[], .price[]
// One global formatter, defined just below doPlugins
function buildProducts(p) {
return p.name.map(function (n, i) {
return [p.category[i], n, p.qty[i], p.price[i]].join(";");
}).join(","); // products joined by comma
}
// Reuse from any rule:
s.products = buildProducts(digitalData.products);Use a debugger to see the products string broken into its products and fields. Adobe's own Experience Platform Debugger is the official option; many practitioners also use a community Chrome extension that lays out each product and parameter clearly (Adobe Analytics debugger extension, third-party, not an Adobe product). Either way these are read-only inspectors, perfect for verifying the string, not for fixing it.
This variable punishes small mistakes without a single error message. The recurring ones:
- A missing semicolon shifts every later value into the wrong slot, with no error.
- Price is the line total (qty × unit), but the Tags "Product String" extension takes the unit price and multiplies, so know which your tool expects.
- A duplicate purchaseID discards the entire hit, every product, eVar, and event on it, which makes refreshable confirmation pages dangerous.
- Reserved characters break the string: never put a comma, semicolon, or pipe inside a product name or value.
- An event missing from
s.eventsis silently ignored, even if it's in the products string. - A conversion-syntax merchandising eVar without its binding event lands in "None."
One last thread to pull. Underneath all its commerce machinery, the products string is really a way to pack many values into a single hit: several products, each with its own events and eVars. That idea, many values carried in one hit, is common enough to have a dedicated variable type of its own, List Variables and List Props, and that is exactly what list variables covers.
Set via implementation/Tags, not report suite settings. Commerce reports appear under Components → Dimensions → Products and the Commerce metrics (Orders, Revenue, Units).
This article focuses on the concepts, architecture, and practical guidance behind the topic. For the latest UI walkthroughs and step-by-step implementation instructions, use the links below. They leave this site and open Adobe's own documentation in a new tab.