Skip to main content

The tracking library you tried to build yourself

Tracking that ships with the component.

Tag it once in the markup, and every place that component gets reused is already tracked. You don't have to remember a dataLayer call or wire up a click listener by hand.

MIT licensedself-hostableTypeScriptnpm packages

Because event instrumentation is real engineering effort

Four problems every tracking setup runs into eventually.

1. Instrumentation never keeps upTagging is a separate task that follows shipping, usually owned by someone else. Every new component, every page, every redesign adds another round of manual setup, and a missing event looks exactly like a quiet afternoon.
2. A vendor switch means a rebuildTracking code is written against one vendor's exact shape. Switch vendors, and every call site gets rewritten, or another tag gets bolted onto a container that's already too full.
3. Compliance is checked after the factData leaves the browser before anyone's consent choice is in the picture, and the compliance check that runs afterward may not catch everything.
4. Your own fields aren't in anyone's schemaVendor schemas validate their own standard parameters, never the loyalty tier, cost center, or deal ID specific to your business. Whoever owns the tag manager ends up manually verifying those against a handwritten spec, page by page, release by release.

Tracking lives in the component

Tag a component once, and every instance is tracked.

The usual way: whether it's a GTM click listener bound to a CSS class or a developer's own dataLayer.push() call, the trigger lives outside the component, added by hand, once, by whoever happened to build that feature. Rename the class, restructure the markup, or just forget the push on the next component, and nothing errors. The event simply stops arriving, or never starts, and it can take weeks before anyone traces a reporting gap back to a change in an unrelated PR.

The walkerOS way: data-elb attributes live directly in the markup, right where the component is built. Tag the button component once, and every place that component is used, on this page or the next hundred, is already tracked. There is no separate tagging pass, and what ships stays in line with what's measured, even when the component is reused somewhere new.

AddToCart.html
<button
  data-elb="product"
  data-elb-product="name:Sneaker;price:89"
  data-elbaction="click:add"
>
  Add to cart
</button>

The attribute sits in the component's source instead of a config someone maintains elsewhere. Ship the component once, and every click it captures reaches every destination, without anyone touching a tag manager.

The vendor becomes a destination

Instrument once, and every vendor is just a mapping.

The usual way: tracking code calls a vendor's SDK directly, in the exact shape that vendor expects. Switch vendors, and every call site gets rewritten, or you add another tag, trigger and variable to a GTM container that's already too full.

The walkerOS way: the app only ever emits one event, named with a strict entity action grammar. Destination configs map it into GA4's shape, Meta's shape, or a warehouse row, so you can add, drop, or swap a destination without changing the app.

productviewordercompletebuttonclick
// same event, three destinations, zero app code touched
{
  "destinations": {
    "ga4": {
      "config": { "mapping": { "product": { "view": { "name": "view_item" } } } }
    },
    "meta": {
      "config": { "mapping": { "product": { "view": { "name": "ViewContent" } } } }
    },
    "warehouse": {
      "config": { "mapping": { "product": { "view": { "name": "product_view" } } } }
    }
  }
}

40+ destination adapters ship with the project. Removing GA4 or adding a warehouse means editing a block in this file instead of re-instrumenting every page.

One place to look instead of three teams to ask

The fields that matter to your business aren't in any vendor's schema.

The usual way: GA4 enforces its own required fields on purchase, but it has never heard of your loyalty tiers, your cost centers, or your internal deal IDs. Whoever owns the tag manager checks those by hand against a handwritten spec, page by page, release by release, and manual checks miss things at scale.

The walkerOS way: your own fields go in the same typed contract as the standard ones, required the same way and validated the same way. A pull request that leaves loyalty_tier out fails before it merges, so no one has to remember to check.

ordercomplete
{
  "contract": {
    "default": {
      "events": {
        "order": {
          "complete": {
            "properties": {
              "data": { "required": ["total", "loyalty_tier"] }
            }
          }
        }
      }
    }
  }
}

Frontend developers write the tagging against this file. Finance and analytics read reports from it. loyalty_tier is required here, and a validate step enforces it, replacing a manual check. Run walkeros validate --strict in CI, and a missing or misspelled field fails the build instead of showing up in a report someone reads weeks later.

The architecture

Three parts, with nothing hidden between them.

Every interaction becomes one typed event, once. What happens before and after is the whole system: where an event comes from, what it's allowed to carry, and where it's allowed to go.

sources

Browser DOM · dataLayer · CMPs · server

Where an interaction is captured: a click, a page view, a server-side call. Nothing is vendor-specific yet.

collector

Mapping · consent · routing

The one place the event is defined, checked against consent, and given a shape, before it reaches anything downstream.

destinations

GA4 · Meta · your warehouse · your own API

40+ adapters ship with the project. Swapping one out takes a config entry, and the event that reaches it stays the same.

walkerOS for ...

Live workshop · free

Tag a real website, live, in the browser.

In one hour, you tag one real page live in the Tag Mode extension and learn the mental model in practice rather than from a diagram: entity and action, properties read off the DOM, context that bubbles, and globals that don't.

60 min (45 + Q&A) · free · live, not recorded · bring your own page · Wednesday 10:00 CEST, date TBD, registrants are notified first

Save your seat

Beyond the repo

Run it yourself, or bring us in for the parts that need a team.

The software stays MIT licensed and self-hostable either way. These exist for the businesses that want a contract behind the pipeline their revenue reporting depends on.

community

Self-hosted

Everything on this page, yours to clone, read, and run. Support comes from GitHub Discussions and the community around the project.

  • MIT licensed, no seat limits
  • All sources, destinations, and transformers
  • Community support, no response-time guarantee
Start free

fixed scope

Implementation

A project rather than a subscription. We align your teams on what to measure and why, define the entity-action event model and mapping config, then build it with your team and hand over a pipeline your engineers own from day one.

  • Business questions mapped to entity action events
  • Event model and mapping design for your stack
  • Migration off an existing tag manager or CDP
  • Custom source or destination builds
Scope a project

SLA

Support

A contract on top of the open-source project, for teams whose collector is on the critical path for revenue reporting or ad spend and can't run on best-effort community replies.

  • Guaranteed response times, direct access to elbwalker engineers
  • Priority fixes and security patches
  • Version and upgrade guidance for production flows
Talk to us

No seat-based pricing on any tier: the software is the same MIT-licensed code whether you're on community or under contract.

FAQ

Questions we actually get.

Can't I just do this with Google Tag Manager?

GTM fans one event out to many vendor tags and has a consent trigger, so it overlaps with the job. As a collection layer it falls short in three ways. Nothing enforces a schema, so meaning lives in a variable-naming convention instead of a validated contract. Logic hides inside hand-written Custom HTML tags that the platform's own consent gate can't see into. And GTM is a router downstream of collection rather than a source of truth: something has to push structured data into dataLayer before GTM can act on it.

Server-side GTM doesn't close that gap. It solves a different, real problem by moving tags off the browser onto infrastructure you control, past blockers and Safari's ITP. The framework underneath stays the same, with a tag, trigger, and variable per vendor, maintained by hand per page, only now on a server you run. walkerOS replaces that framework, and it can run alongside a server-side GTM container if you already have one.

Some organizations rule out a Google-owned tag manager by policy. Others self-host on principle, so they don't have to add a new subprocessor to a DPA or send one to legal for review. walkerOS runs inside your own cloud, which settles both cases before they come up.

GTM handles fan-out well enough that most teams never look past it, until the tag count, the missing contract, and the developer work of filling dataLayer by hand for every new event become the problem. That is the same dependency on developers GTM was supposed to remove.

If we move collection to walkerOS, don't we lose the visual point-and-click tagging marketing uses?

No. GTM becomes one destination among many instead of the collection layer itself, so it goes back to the one job it was built for, firing tags, and gets easier to work in. Point-and-click tag additions still work for marketing, wired off the same typed events.

GTM no longer has to carry the collection logic: the custom scripts, hand-rolled consent checks, and schema hacks that pile up in a container once it's the only place data gets shaped. The container ends up smaller, with less code nobody wants to touch and more control over what's in it.

We want to build a tracking library in-house. Why would we adopt this instead?

Because adopting walkerOS doesn't ask you to stop building. Teams that build their own measurement platform still don't want to reinvent the collection layer from scratch. walkerOS is self-hosted and MIT licensed, so it slots in as one component under an architecture you own, without tying you to a platform vendor.

Own the thing your growth numbers depend on.