Module 02 · The Data Layer

The contract
between dev and
analytics.

A data layer is the boring piece of JavaScript that determines whether your implementation is maintainable or fragile. We will design one, defend it from feature creep, and learn what to do when the developers refuse to build it.

Reading35 min
LabBuilder
DifficultyIntermediate
Pre-reqModule 01

1 · What a data layer is

A data layer is one JavaScript object — usually called digitalData, dataLayer, or adobeDataLayer — that exposes the meaning of a page in a form that any analytics, personalisation, or advertising tag can read.

window.digitalData = {
  page: {
    name: "Product Detail: ASTRO-3000 Telescope",
    type: "product",
    category: "telescopes",
    language: "en-US"
  },
  user: {
    loginStatus: "logged-in",
    customerId: "c_847221",
    segment: "premium"
  },
  product: [{
    sku: "ASTRO-3000",
    name: "Astro 3000 Telescope",
    category: "telescopes",
    price: 499.00,
    inStock: true
  }]
};

That is the entire idea. A single, well-named object, populated by the application before any tag fires, so that the tag's job is reduced to copying values into Adobe variables.

2 · Why it exists

Without a data layer, your tag manager ends up scraping the DOM:

// please do not do this
const productName = document.querySelector('h1.product-title').innerText;
const price = document.querySelector('.price-now').innerText.replace('$', '');
const inStock = document.querySelector('.add-to-cart').disabled === false;

Every one of those selectors is a time bomb. A designer renames a CSS class and your analytics silently stops working. A new template ships and prices are suddenly reported as "$499.00" instead of 499. The analytics team and the front-end team are now in a permanent state of low-grade conflict.

A data layer ends this. The application makes a promise — "I will put the product price at digitalData.product[0].price as a number" — and the tag manager makes a promise — "I will only read from there, never from your HTML." Both teams can now refactor independently.

The shipping container analogy

Think of the data layer as the standardised shipping container of the analytics world. The application packs whatever it wants into the container. The tag manager doesn't care what's inside, only that the container's shape is fixed. As long as both ends agree on the shape, the contents can change without coordination.

3 · The three shapes you will encounter

3.1 The flat object

window.digitalData = {
  pageName: "Home",
  pageType: "home",
  userStatus: "anonymous",
  cartValue: 0
};

Simplest possible. Works for small sites. Breaks down once you have multi-valued data (a list of products, multiple search facets).

3.2 The nested object — CEDDL-flavoured

The W3C Customer Experience Digital Data Layer spec from 2013. Not officially adopted, but every major analytics consultancy uses something resembling it.

window.digitalData = {
  page: {
    pageInfo: { pageID: "abc123", pageName: "Home", destinationURL: "https://…" },
    category: { primaryCategory: "home", subCategory: "" },
    attributes: { country: "US", language: "en" }
  },
  user: [{
    profile: [{
      profileInfo: { profileID: "u_001", returningStatus: "new" },
      address: { country: "US" }
    }]
  }],
  product: [],
  cart: { price: { basePrice: 0, currency: "USD" }, item: [] }
};

Yes, user is an array. Yes, user[0].profile is also an array. The CEDDL spec allows multiple users (e.g. a parent buying for a child) and multiple profiles per user. In practice most teams use index [0] and pretend the arrays aren't there. The deep nesting is intentional — it gives a stable home for every piece of context — but it does make the Launch data elements verbose.

3.3 The event-driven layer — ACDL

Modern. Instead of a static object that gets re-written on each page, the application pushes events into the layer and listeners pick them up.

window.adobeDataLayer = window.adobeDataLayer || [];

// On page load
adobeDataLayer.push({
  event: "page loaded",
  page: { name: "Home", type: "home" }
});

// Later, when the cart changes
adobeDataLayer.push({
  event: "cart updated",
  cart: { value: 49.99, itemCount: 1 }
});

This is the Adobe Client Data Layer (ACDL), Adobe's modern recommendation. We will cover its mechanics in section 5.

4 · CEDDL in detail

If you inherit an existing implementation, it is overwhelmingly likely to look like CEDDL. So you must be able to read it fluently.

The standard top-level keys

KeyHolds
pageInstanceIDA unique ID for this page render.
pagePage-scoped metadata — name, type, category, language.
product (array)Products displayed or interacted with.
cartCart contents and totals.
transactionOrder data on the confirmation page.
event (array)Events fired on this page (search, video play, error).
component (array)Reusable components present on the page.
user (array)Visitor identity & profile.
privacyConsent flags.
versionThe data layer schema version. Track this.

The pattern in practice

The application populates digitalData before the tag manager loads. This is the rule. If the tag manager loads first, it reads an empty object and your tracking is wrong.

<head>
  <!-- 1. Build the data layer -->
  <script>
    window.digitalData = {
      page: { pageInfo: { pageName: "Home" } },
      user: [{ profile: [{ profileInfo: { returningStatus: "new" } }] }]
    };
  </script>

  <!-- 2. Load the tag manager -->
  <script src="https://assets.adobedtm.com/.../launch-EN12345.min.js"></script>
</head>

In Launch you then build a data element for each value you care about:

// Data Element "Page Name" — Custom Code
return digitalData?.page?.pageInfo?.pageName || "";

And your tracking rule sets the AppMeasurement variable from the data element. No DOM scraping. No fragile selectors. If the data layer changes shape, you fix it in one place.

5 · Adobe Client Data Layer (the modern way)

ACDL is a small open-source library that gives you an event bus on top of a data layer. The application pushes events; consumers subscribe; the library handles the bookkeeping.

Setting up

<script>
  window.adobeDataLayer = window.adobeDataLayer || [];
</script>
<script src="https://cdn.adobe.io/adobe-client-data-layer/2.0.0/adobe-client-data-layer.min.js"></script>

Pushing data and events

// Set state — no event, just an update
adobeDataLayer.push({
  user: { loginStatus: "logged-in", segment: "premium" }
});

// Fire an event — anything subscribed to it runs
adobeDataLayer.push({
  event: "cm:product viewed",
  eventInfo: { path: "product" },
  product: { sku: "ASTRO-3000", name: "Astro 3000", price: 499.00 }
});

Subscribing

adobeDataLayer.addEventListener("cm:product viewed", function(event) {
  // event === the object that was pushed
  s.eVar4 = event.product.sku;
  s.eVar5 = event.product.name;
  s.events = "prodView";
  s.linkTrackVars = "events,eVar4,eVar5";
  s.linkTrackEvents = "prodView";
  s.tl(true, "o", "Product View");
});

The ACDL extension for Launch wraps this for you, so in the Launch UI you can simply create rules with the event "Adobe Client Data Layer — Event" and pick the event name from a dropdown.

Key behaviour

ACDL replays events to listeners that subscribe late. If the application pushed page loaded at t=200ms and Launch subscribed at t=900ms, the listener still fires. This solves the eternal race-condition problem.

6 · Design checklist

Before you commit a data layer to a release, run this checklist. Each item is the result of someone, somewhere, painfully learning a lesson.

Naming

  • One name to rule them all. Pick digitalData or adobeDataLayer and never have both. Two data layers is two sources of truth, which is zero sources of truth.
  • Lower-case keys. JavaScript is case-sensitive and humans are not. pageName and pagename will cohabit a real codebase until you stop them.
  • Spell out abbreviations. productCategory, not prodCat.

Types

  • Numbers as numbers. price: 499.00, not price: "$499.00".
  • Booleans as booleans. inStock: true, not "yes".
  • Dates as ISO 8601. "2026-05-19T10:30:00Z", never a regional format.
  • Empty means absent. Use null or omit the key. Never put the string "unknown" or "n/a" in a value field.

Timing

  • Populated before tag manager loads. Inline <script> ahead of the Launch embed code.
  • For SPAs, push events on route change. Module 07 covers this.
  • Asynchronous values (auth, geo) push when ready. Don't block page render to wait for them — push an event when they resolve and let the listeners react.

Scope

  • Page-scoped values reset on each page. Don't let stale cart data from page A appear on page B.
  • Session-scoped values persist. User identity, AB test variant, consent.
  • Document what is which. Future-you needs this.

Versioning

  • Include a version field. When you change the schema, bump it. Subscribers can branch on version.

7 · When the developers say no

You will spend a non-trivial amount of your career trying to get engineering teams to populate a data layer that they do not consider their problem. Three approaches, in order of how much you should prefer them:

Approach 1 — Make it cheap

Don't ask for a data layer. Ask for their existing internal data — the props they pass to their components, the JSON they fetch from their API — to be exposed as a global. If the page already has a window.__INITIAL_STATE__, that is your data layer. Hide your scraping inside Launch data elements and present them with a one-line ask.

Approach 2 — Build it together

Sit with a senior front-end engineer and design the schema in their language. Use TypeScript interfaces if they like types. Get them to own the data layer as a piece of public API, the way they own their API contracts. They will protect it once it's theirs.

Approach 3 — Resort to selectors

You will sometimes have to. When you do, write the selectors as a shim in Launch's custom-code data elements, not as ad-hoc code inside rules. Centralise the fragility. When the CSS class names change you fix one file, not eighteen.

8 · Practice

Open the Data Layer Builder lab. You will design a data layer for a fictional e-commerce site (the AstroCart example we'll use throughout the course). The lab generates the JSON for you and shows how a Launch data element would consume each field.

Exercise

Sketch the data layer for the three most important page types on a site you know: the home page, a product page, and a confirmation page. What's the same on all three? What changes? When you've written it down, compare against the CEDDL spec in section 4 — and notice how often you converge.