Skip to main content

walkerOS for developers

Tracking that ships with the feature, reviewed in the same pull request.

Tag components with data-elb attributes or call elb() from your code. The browser source turns those into events, the collector adds consent, user and globals, and each destination maps them into its vendor format, all configured in TypeScript or in a flow.json you review like any other file.

Sounds familiar

Tracking bugs that end up on your desk

The router changes the URL without a page load. Now page_view never fires, or fires twice once someone adds a History Change trigger on top of Enhanced Measurement.The browser source does not try to detect route changes. You call elb('walker run') when your router navigates, which fires one page view and re-scans the page for tagged elements. The React and Next.js quickstarts show where the call goes and how to skip it on the first render.
The tag fires before the dataLayer.push it depends on and sends undefined. It works in preview and on slow pages, and loses data on fast ones.A tagged click is resolved in the capture phase, from the DOM as it is at click time and before your own click handlers run, so the event reads the data-elb-* values of the element that was clicked. Calls queued on window.elbLayer before walkerOS loads are processed once it starts. See the click trigger in the HTML attributes reference.
The feature ships Thursday. Its tracking ticket is still in the backlog, because tracking lives in another system with its own workflow and loses every prioritization fight.The tracking is data-elb markup in the component file, so it lands in the same pull request as the feature and gets the same review. In JSX, createTagger builds the attributes as an object you spread onto the element and escapes the values for you.
Every tracked interaction is a hand-written call in one vendor's format, so a vendor switch or even a spec update means touching every call site.Your markup and elb() calls use your own entity and action names. The vendor's event names and fields live in each destination's mapping, so the next vendor change is a config diff.

Tagging

Tracking attributes live in the component file

data-elb names the entity, data-elb-product holds its properties, and data-elbaction pairs a trigger such as click or visible with the action to fire. The browser source reads those values from the DOM when the trigger fires, so a component that moves or rerenders takes its tracking along. Renaming CSS classes in a redesign leaves it alone.

<div data-elb="product" data-elbaction="visible:view">
  <h2 data-elb-product="name:Everyday Ruck Snack">Everyday Ruck Snack</h2>
</div>

Fires product view with data: { name: 'Everyday Ruck Snack' } once the card has been on screen for a second, and again each time it comes back into view. Reference: HTML attributes.

Contracts

Write down what an event must contain, and drop what breaks it

A contract is JSON Schema keyed by entity and action, kept in the top-level contract block of flow.json. A validate transformer references it with $contract.<name>. In strict mode an event that fails is dropped before any destination sees it; in pass mode it continues with source.valid set to false, so a later step can route it. Contracts can extend each other, which keeps a shared base and a per-site addition in one file.

{
  "version": 4,
  "contract": {
    "web": {
      "events": {
        "order": {
          "complete": {
            "properties": { "data": { "required": ["total", "currency"] } }
          }
        }
      }
    }
  },
  "flows": {
    "default": {
      "transformers": {
        "validate": {
          "package": "@walkeros/transformer-validate",
          "config": {
            "settings": { "contract": ["$contract.web"], "mode": "strict" }
          },
          "next": "ga4"
        }
      }
    }
  }
}

An order complete without data.total or data.currency stops at this step and never reaches ga4. Inheritance and wildcards: contract.

Testing

Catch a broken event in CI

Each step in a flow can carry named { in, out } examples. walkeros validate flow.json checks that connected steps fit together and that every example satisfies the contract. With --strict a violation counts as an error and the command exits non-zero, which fails the build. walkeros push --simulate runs one event through the flow with the named destination mocked and reports whether it accepted the event, without calling the vendor.

# Fails the build if an example breaks the contract
walkeros validate flow.json --strict

# Runs one event with the ga4 destination mocked
walkeros push flow.json --simulate destination.ga4 --event '{"name":"page view","data":{"title":"Home"}}'

Example format: step examples. Every command and flag: CLI reference.

Where it fits

walkerOS runs next to what already sends events

What stays

  • Your dataLayer.push calls and your GTM container. They keep running, and the dataLayer source can read the same pushes into walkerOS while you move to data-elb one component at a time.
  • Your analytics vendors. Each one becomes a destination with its own mapping, so adding or swapping one is a config change.
  • Your build, code review and CI. A flow is TypeScript in your app or a flow.json in the repo, so it goes through the same pull requests as the rest of your code.
  • Your naming. Entities and actions come from your product, written as entity action with a space.

What changes

  • Tracking moves into component markup or your own elb() calls and gets reviewed with the feature.
  • Vendor event names and fields move out of your code into one mapping per destination.
  • A contract in flow.json states what each event must contain, and a validate transformer drops or flags events that break it.
  • You pick a mode. Integrated runs the collector inside your app, typed and configured through startFlow. Bundled keeps the setup in flow.json, and the CLI builds it into a separate file.

Run npm install @walkeros/collector @walkeros/web-source-browser, add the console destination from the quickstart, then tag one component with data-elb and watch its event in the console before you connect a real vendor.

What walkerOS does not do for you

  • Contracts are JSON Schema you write by hand. Nothing generates one from a tracking plan spreadsheet or infers it from the events you already send.
  • Tagging an existing app is work you do component by component. walkerOS does not scan an untagged site to add attributes, and it does not convert a GTM container.
  • walkeros validate checks the examples in your flow, not your real components. A release that drops a data-elb-product attribute still passes it, and the gap shows up only at runtime, when a validate transformer checks the live event.
  • walkeros push --simulate reports success or failure per destination. It does not print the vendor payload, so to inspect that you need the flow_simulate tool of the MCP server with verbose set.
  • When contracts extend each other, only properties and required merge. Keywords such as oneOf, enum and allOf take the child value, so combine those in one schema yourself.

Questions

Questions before you start

Why name events "product view" when "Product Viewed" reads better for stakeholders?

The entity comes first because the rest hangs off it: data-elb-product holds the properties, a destination mapping is keyed product.view, and a contract rule under product with the * action applies to every product action. Free-form names drift, which is how one tool ends up with page_view and another with Page Viewed. A friendlier label for stakeholders belongs in the report. See product view become GA4 view_item in the mapping example.

Our tracking is DOM-based in GTM and mostly works. What changes, and what do we keep?

You keep the container and your dataLayer.push calls, and you add data-elb attributes next to them one component at a time. The failure mode is what changes: a GTM trigger on a CSS selector is separate from the markup it depends on, so a template change can break it without anyone noticing, while a data-elb attribute sits on the element and moves with it. Retire each old trigger once the tagged component sends what you expect. The HTML attributes reference has the full syntax to compare against your triggers.

How do I know a mapping or contract change is right before it deploys?

Give the step a named { in, out } example and run walkeros validate flow.json --strict in CI. It fails when an example breaks the contract or when connected steps don't fit. walkeros push flow.json --simulate destination.<name> then runs a real event through the flow with that destination mocked. The validate guide walks through breaking a contract on purpose so you can see the error it prints.

Next steps

Where to go from here

walkerOS for ...