Skip to main content
Source code Package

Browser source

Captures user interactions directly from the DOM using data-elb-* attributes. Extracts tracking data from markup, tracks pageviews automatically or manually, exposes an enhanced elb function with walker init support, and provides a trigger system covering click, submit, load, hover, scroll, and visible.

Where this fits

The browser source is a source in the walkerOS flow:

It reads `data-elb-*` attributes from your HTML and generates events. The Collector routes these to your configured destinations.

Installation

Install the packages:

npm install @walkeros/collector @walkeros/web-source-browser @walkeros/web-source-session

Configure in your code:

import { startFlow } from '@walkeros/collector';
import { sourceBrowser } from '@walkeros/web-source-browser';
import { sourceSession } from '@walkeros/web-source-session';

const { collector, elb } = await startFlow({
sources: {
  browser: sourceBrowser,
  session: sourceSession, // Add for session tracking
},
});

Configuration

This source uses the standard source config wrapper (consent, data, env, id, ...). For the shared fields see source configuration. Package-specific fields live under config.settings and are listed below.

Settings

PropertyTypeDescriptionMore
prefixstringPrefix for data attributes (default: data-elb)
scopestringDOM scope for event tracking (default: document)
pageviewbooleanEnable automatic pageview tracking
capturebooleanRead click/submit triggers in the capture phase so tagged elements are resolved at click time before app handlers run (set false for the previous bubble-phase behavior)
elbstring | booleanName for global elb function, or false to install none
namestringCustom name for source instance
elbLayerboolean | string | anyEnable elbLayer for async command queuing (boolean, string, or Elb.Layer)

Mapping

This package does not define custom rule-level settings. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.

Examples

Click event

A button click with walker data attributes is captured as an entity action event with the mapped label data.

Event
"<button data-elb=\"cta\" data-elb-cta=\"label:Sign Up\" data-elbaction=\"click:click\">Sign Up</button>"
Out
elb({
  "name": "cta click",
  "entity": "cta",
  "action": "click",
  "data": {
    "label": "Sign Up"
  },
  "context": {},
  "globals": {},
  "nested": [],
  "source": {
    "type": "browser",
    "platform": "web",
    "url": "https://example.com/",
    "referrer": ""
  },
  "trigger": "click"
})

Context and globals

Walker context and globals attributes attach ambient metadata to events emitted by the browser source.

Event
"<div data-elbcontext=\"test:engagement_flow\" data-elbglobals=\"language:en;plan:premium\"><div data-elb=\"cta\" data-elb-cta=\"label:Try Now\" data-elbaction=\"click:signup\">Try Now</div></div>"
Out
elb({
  "name": "cta signup",
  "entity": "cta",
  "action": "signup",
  "data": {
    "label": "Try Now"
  },
  "context": {
    "test": [
      "engagement_flow",
      0
    ]
  },
  "globals": {
    "language": "en",
    "plan": "premium"
  },
  "nested": [],
  "source": {
    "type": "browser",
    "platform": "web",
    "url": "https://example.com/",
    "referrer": ""
  },
  "trigger": "click"
})

Data attribute types

Walker data attributes parse scalar, boolean, and array values into typed fields on the emitted event.

Event
"<div data-elb=\"product\" data-elb-product=\"price:99.99;available:true;colors[]:red;colors[]:blue\" data-elbaction=\"click:select\"></div>"
Out
elb({
  "name": "product select",
  "entity": "product",
  "action": "select",
  "data": {
    "price": 99.99,
    "available": true,
    "colors": [
      "red",
      "blue"
    ]
  },
  "context": {},
  "globals": {},
  "nested": [],
  "source": {
    "type": "browser",
    "platform": "web",
    "url": "https://example.com/",
    "referrer": ""
  },
  "trigger": "click"
})

Impression event

An intersection observer impression on a div with walker attributes emits a banner view event.

Event
"<div data-elb=\"banner\" data-elb-banner=\"type:promotional;position:sidebar\" data-elbaction=\"impression:view\"></div>"
Out
elb({
  "name": "banner view",
  "entity": "banner",
  "action": "view",
  "data": {
    "type": "promotional",
    "position": "sidebar"
  },
  "context": {},
  "globals": {},
  "nested": [],
  "source": {
    "type": "browser",
    "platform": "web",
    "url": "https://example.com/",
    "referrer": ""
  },
  "trigger": "impression"
})

Nested entities

A page load produces a page view plus a product view whose nested size entity is captured on the product event.

Event
"<div data-elb=\"product\" data-elb-product=\"id:SKU-42;name:Sneakers\" data-elbaction=\"load:view\"><div data-elb=\"size\" data-elb-size=\"selected:large;inStock:true\"></div></div>"
Out
elb({
  "name": "page view",
  "data": {
    "domain": "example.com",
    "title": "",
    "referrer": "",
    "id": "/"
  },
  "context": {},
  "globals": {},
  "trigger": "load",
  "source": {
    "type": "browser",
    "platform": "web",
    "url": "https://example.com/",
    "referrer": ""
  }
});

elb({
  "name": "product view",
  "entity": "product",
  "action": "view",
  "data": {
    "id": "SKU-42",
    "name": "Sneakers"
  },
  "context": {},
  "globals": {},
  "nested": [
    {
      "entity": "size",
      "data": {
        "selected": "large",
        "inStock": true
      },
      "context": {},
      "nested": []
    }
  ],
  "source": {
    "type": "browser",
    "platform": "web",
    "url": "https://example.com/",
    "referrer": ""
  },
  "trigger": "load"
})

Page view

A page load trigger captures the current URL, title, and referrer as a walker page view event.

Event
""
Out
elb({
  "name": "page view",
  "data": {
    "domain": "example.com",
    "title": "Documentation",
    "referrer": "https://example.com/",
    "id": "/docs"
  },
  "context": {},
  "globals": {},
  "trigger": "load",
  "source": {
    "type": "browser",
    "platform": "web",
    "url": "https://example.com/docs",
    "referrer": "https://example.com/"
  }
})

Submit event

A form submit with walker data attributes produces a signup complete event carrying the selected plan.

Event
"<form data-elb=\"signup\" data-elb-signup=\"plan:premium\" data-elbaction=\"submit:complete\"></form>"
Out
elb({
  "name": "signup complete",
  "entity": "signup",
  "action": "complete",
  "data": {
    "plan": "premium"
  },
  "context": {},
  "globals": {},
  "nested": [],
  "source": {
    "type": "browser",
    "platform": "web",
    "url": "https://example.com/",
    "referrer": ""
  },
  "trigger": "submit"
})

Enhanced elb

The browser source returns an enhanced elb function with additional features:

// Collector elb: Basic event tracking
collector.push('product view', { id: 'P123' });

// Browser source elb: Enhanced with DOM commands
elb('walker init', document.querySelector('#app'));
elb('product view', { id: 'P123' }); // Also tracks events

Browser source elb adds:

  • DOM commands: walker init for asynchronous DOM element loading
  • Flexible arguments: support for multiple argument patterns
  • elbLayer integration: automatic processing of queued commands
  • Element parameters: support for element parameters in commands

See Commands for complete API documentation.

Multiple sources on one page

Any number of browser sources can run on one page. Each keeps its own triggers, its own element registry, and its own visibility state, so nothing one source wires, times, or tears down reaches another.

Two page-level resources are the exception, because each is a single slot:

ResourceSettingRule
The writer installed at window[elb]elbThe first source to install the name keeps it.
The command queue at window[elbLayer]elbLayerThe first source to adopt the array keeps it.

A source that asks for a name another source already holds is refused that one resource and logs an error. Everything else keeps working: its scope is still scanned, its triggers still fire, and its own push still resolves. A page's own bootstrap, the elb stub function and pre-filled elbLayer array defined before the source loads, belongs to no source and is adopted as usual.

The DOM is not owned. Any number of sources may scan the same node, document included. That is how two flows run on one page: give each its own elb name and its own elbLayer name (or false), and let them share the markup.

A vendor flow beside the page's own

Distinct names on both single-slot resources mean nothing is refused on either side. pageview: false leaves the page view event to the walker the page already runs, and the scope narrows the vendor's delegation to the subtree it cares about.

import { startFlow } from '@walkeros/collector';
import { sourceBrowser } from '@walkeros/web-source-browser';

const { collector, elb } = await startFlow({
sources: {
  browser: {
    code: sourceBrowser,
    config: {
      settings: {
        elb: 'vendorElb',
        elbLayer: 'vendorLayer',
        scope: document.body,
        pageview: false,
      },
    },
  },
},
});

An embedded overlay with no window footprint

elb: false installs no window property at all and elbLayer: false creates no queue, so the source adds nothing to the page window and reaches its collector through the elb that startFlow returns. Pair it with an explicit scope and the overlay captures only its own markup.

import { startFlow } from '@walkeros/collector';
import { sourceBrowser } from '@walkeros/web-source-browser';

const host = document.createElement('div');
document.body.appendChild(host);
const overlay = host.attachShadow({ mode: 'closed' });

const { collector, elb } = await startFlow({
sources: {
  browser: {
    code: sourceBrowser,
    config: {
      settings: {
        elb: false,
        elbLayer: false,
        scope: overlay,
        pageview: false,
      },
    },
  },
},
});

One elbLayer belongs to one walker

A queue is drained once. The source that adopts a named layer reads every entry, and a command like walker run has exactly one addressee, so a second source on the same array would starve the first rather than join it. Parallel flows use separate layer names or elbLayer: false, and share instrumentation through the data-elb attributes rather than through a queue.

Overlapping scopes fire once per source

Two sources scanning the same tagged element wire it twice. click, hover, pulse, scroll, wait, impression, and visible each fire once per source, into that source's own pipeline, and an observed element carries one dwell timer per source.

The prefix setting decides whether that is what you want:

  • Same prefix is deliberate dual collect. One set of attributes feeds both pipelines, and each collector sees the interaction once.
  • Different prefix isolates the two. Each source only sees elements tagged for it, so overlapping scopes cost nothing.
note

Give overlapping sources the same scope root. A source discovers data-elbobserve containers inside its own scope only. When two sources share a prefix and the container sits outside one of their scopes, only the source that discovered it reaps elements the container removes. For the other one that element's teardown never runs: its pulse intervals keep firing, its wait timeouts stay armed, and its hover and visibility registrations stay in place against a node that has left the document, which also keeps that node referenced until the source is torn down. A shared scope root means both sources see the same containers.

Known limits

  • Version skew. A page running an older release of the package leaves no ownership mark, so a source injected later reads that layer as free and adopts it. This only matters when both use the same layer name, which is what the contract above tells parallel flows not to do.
  • Integrations that assign window[elb] themselves. The walker.js bundle assigns the name after the source has installed its writer, which leaves a value in the slot that belongs to no source, so a second source reads the name as free and installs over it.
  • The same script included twice. Each copy scans the same markup into its own pipeline, so every event is collected twice. That is a page misconfiguration rather than something the source can resolve, and it is diagnosable: both refusals log at initialization.

Next steps

💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)