Key takeaways
- The data layer is a contract between analytics and engineering — design it before any implementation begins
- Push the data layer object before the Adobe Launch embed code loads on every page
- Use a consistent object hierarchy: page, user, product, cart — and never flatten everything to the top level
- Event-driven data layer pushes for interactions are cleaner than DOM scraping in Launch rules
- Version your data layer schema and document changes — it is infrastructure, not an implementation detail
In this article
The data layer is the single most important piece of infrastructure in any Adobe Analytics implementation. It is also the most frequently skipped. Teams jump straight into Adobe Launch, start writing rules that scrape values off the DOM, and six months later have an implementation that breaks every time a developer changes a CSS class name or rewrites a page template.
I have audited dozens of Adobe Analytics implementations. In every case where data quality problems existed, they could be traced back to a weak or missing data layer. This guide covers how to design, implement, and maintain a data layer that makes your Adobe Launch implementation reliable, scalable, and easy to hand over.
What a data layer actually is
A data layer is a JavaScript object that lives on every page of your website and exposes structured, consistent information about the page and its context. It is the bridge between what your website knows and what your analytics tools need to know.
Think of it as an API that your website publishes for analytics and marketing tools to consume. The website owns the data. The data layer makes that data available in a predictable, structured format. Adobe Launch reads from it. Adobe Analytics, Adobe Target, and any other tags consume it through Launch.
The data layer should be treated exactly like a public API. It has a schema, it is versioned, it has documentation, and changes to it go through a review process. The moment you treat it as an afterthought is the moment your analytics data becomes unreliable.
Why it matters specifically for Adobe Launch
Adobe Launch reads values through data elements. Data elements can be configured to read from multiple sources — CSS selectors, JavaScript variables, cookies, query strings, or a data layer. Of these, only the data layer gives you values that are:
- Stable — values do not change when engineers update markup or CSS
- Intentional — values are explicitly set by the engineering team, not inferred from UI elements
- Available early — values are present before the DOM finishes rendering
- Testable — values can be validated in console before Launch even fires
Designing the schema
The data layer schema should be designed before any implementation work begins — by the analytics team, in collaboration with engineering. The schema defines:
- Which objects exist (page, user, product, cart, etc.)
- Which properties each object contains
- What data type each property holds (string, number, boolean, array)
- Which pages populate which objects
- What the default/empty value is when data is not available
A well-structured schema uses a clear hierarchy rather than a flat structure. Here is the difference:
// Hard to read, no clear ownership, naming collisions likely window.digitalData = { pageName: "Home", pageType: "homepage", userId: "", loginStatus: "logged-out", productId: "", productName: "" };
// Clear ownership, easy to extend, maps cleanly to eVars window.digitalData = { page: { name: "Home", type: "homepage", section: "marketing", language: "en-IN" }, user: { loginStatus: "logged-out", userId: "", // hashed — never PII segment: "" }, product: { // populated on product pages id: "", name: "", category: "", price: 0 }, cart: { // populated on cart/checkout items: [], total: 0, itemCount: 0 }, site: { environment: "production", version: "3.1.0" } };
Never store personally identifiable information in the data layer. No email addresses, names, phone numbers, or raw user IDs. If you need to track users, use a hashed or anonymised identifier only. The data layer is accessible to anyone who opens DevTools on your site.
Page load data layer pattern
The data layer must be pushed before the Adobe Launch embed code. This is the most important timing rule. If Launch fires before the data layer is populated, data elements will return empty values on the first event.
Push the data layer object in the <head>
Place the data layer initialization as the first script in your page's <head> — before GTM, before Launch, before anything else. This guarantees values are available when Launch reads them.
Populate values server-side when possible
The most reliable data layer values come from the server — page type, login status, product IDs, prices. These are known at render time and never depend on JavaScript execution completing. Use client-side JS only for values that genuinely are not available server-side.
Use empty strings, not null or undefined
When a property has no value for a given page, set it to an empty string — never null, undefined, or omit the key entirely. Launch data elements will return undefined for missing keys, which can cause eVar values to be "(not set)" in your reports.
Initialize every property on every page
The full schema should be present on every page, even if most properties are empty strings. This prevents data from the previous page persisting in memory on single-page applications.
<head> <!-- 1. Data layer FIRST --> <script> window.digitalData = { page: { name: "Product:Running Shoes", type: "product" }, user: { loginStatus: "logged-in", userId: "u_8f3k2" }, product: { id: "SKU-4421", name: "Trail Runner X", price: 4999 } }; </script> <!-- 2. Adobe Launch embed code AFTER data layer --> <script src="//assets.adobedtm.com/your-launch-embed.min.js" async></script> </head>
Event-driven pushes for interactions
The page load data layer covers page-level context. For user interactions — button clicks, form submissions, video plays, add-to-cart — you need event-driven data layer pushes. These are additional pushes that fire when the interaction occurs, adding interaction-specific data to the data layer before Launch fires its rule.
// Fired by your engineering team when user clicks Add to Cart document.querySelector('.add-to-cart-btn').addEventListener('click', function() { // Push interaction data to the data layer window.digitalData.event = { name: "addToCart", category: "ecommerce" }; window.digitalData.cart = { lastAddedProduct: { id: "SKU-4421", name: "Trail Runner X", price: 4999, quantity: 1 } }; // Trigger a custom event for Launch to listen to document.dispatchEvent(new CustomEvent('digitalData:addToCart')); });
In Adobe Launch, create a rule that listens for this custom event:
Rule name: Event — Add to Cart
Event:
Type: Custom Event
Name: digitalData:addToCart
Actions:
1. Set Variables
event1 = 1 (Add to Cart event)
eVar5 = %DL - Last Added Product ID%
eVar6 = %DL - Last Added Product Name%
2. Send Beacon (s.tl())
Reading the data layer in Adobe Launch
In Launch, configure your data elements to read from the data layer using the JavaScript Variable type or the Data Layer type if you are using the Adobe Client Data Layer extension.
Name: DL - Page Name Type: JavaScript Variable Path: digitalData.page.name Default value: (empty) Duration: None Name: DL - Login Status Type: JavaScript Variable Path: digitalData.user.loginStatus Default value: (empty) Duration: None Name: DL - Product ID Type: JavaScript Variable Path: digitalData.product.id Default value: (empty) Duration: None
Always set Duration to "None" on data elements that read from the data layer. This forces Launch to re-read the value fresh on every rule execution rather than using a cached value from a previous page — which is critical on single-page applications where the URL changes but the page does not reload.
Naming conventions
Consistent naming across the data layer, data elements, and eVar mapping is what makes implementations maintainable. Here is the convention I use on every engagement:
| Layer | Format | Example |
|---|---|---|
| Data layer key | camelCase, nested objects | digitalData.page.name |
| Launch data element | DL - [Object] [Property] | DL - Page Name |
| Launch rule | [Trigger] — [Action] | Page Load — Send Beacon |
| eVar mapping | eVar[N] = [DL element] | eVar1 = %DL - Page Name% |
| Custom events | digitalData:[eventName] | digitalData:addToCart |
Document this convention in a shared specification that both analytics and engineering have access to. The spec should be a living document — updated every time the schema changes, treated with the same rigour as API documentation.
Common mistakes
- Pushing the data layer after Launch. If Launch loads first, it fires before your data is available. All your data elements return empty on the page load event. Always: data layer first, Launch second.
- Using null or undefined instead of empty strings. JavaScript Variable data elements return undefined for null/undefined values. In eVars this shows as "(not set)" which pollutes reports and makes filtering impossible.
- Not reinitialising on SPA route changes. Single-page applications do not reload the page on navigation. The data layer from the previous route persists unless you explicitly reset it. Push a fresh data layer object on every client-side route change.
- Storing PII in the data layer. Email addresses, names, and phone numbers in the data layer are visible to anyone who opens DevTools. Use hashed IDs only.
- No documentation or versioning. When the data layer is undocumented, every developer interprets it differently. Three months later nobody knows what half the properties mean or which pages push them.
- Having engineering own the schema without analytics input. Engineers will build what makes sense for the application. Analytics needs the schema to map to business questions. The spec must be a collaboration — analytics defines what is needed, engineering defines what is feasible.
Data layer checklist
Use this before signing off on any data layer implementation:
- Data layer is pushed before the Launch embed code on every page
- Full schema is present on every page — no missing objects
- Empty values use empty strings, not null or undefined
- No PII stored in any data layer property
- Product and cart objects are populated correctly on relevant page types
- Custom events dispatch correctly and Launch rules listen to them
- SPA route changes trigger a data layer reset and re-push
- Schema is documented and version-controlled
- All Launch data elements use Duration: None
- Data layer values validated in console before Launch QA begins