tracklane

One interface between your application and every tool that receives events.

Get startedGitHubpnpm add tracklane

The same event, both sides

Two independent libraries that share only the vocabulary — which is what makes an event mean the same thing in the browser and in your backend.

in the browser
import { createTracking, ga4 } from 'tracklane/browser';

const { track, identify, consent } = createTracking({
  providers: [ga4('G-XXXXXXX')],
  onError: (error) => report(error),
});

track('purchase', { transaction_id: 'T-1024', value: 49.9, currency: 'BRL' });
identify({ userId: 'user_42', email: 'ana@example.com' }, { plan: 'pro' });
consent('update', { ad_storage: 'granted', analytics_storage: 'granted' });
on your server
import { createTracking, ga4 } from 'tracklane/server';

const { track } = createTracking({
  providers: [ga4({ measurementId: 'G-XXXXXXX', apiSecret })],
});

await track('purchase', order, {
  cookies: request.headers.get('cookie'),
  user: { userId: order.userId, email: order.email },
  dedupId: order.id,
  timestamp: order.paidAt,
});

One event, every tool

Name what happened once, in GA4 vocabulary. Each provider translates it into its own. Adding a tool is a line of configuration, not a diff across your application.

Browser and server

Two independent libraries sharing only the vocabulary, so an event means the same thing on both sides. The conversion APIs are half the problem, and each provider covers both.

No opinions

It never withholds a send on policy grounds, holds no consent state, and installs no vendor tags. What reaches a tool is what a hand-written call would have sent — including the errors.

Nothing you did not ask for

No queue, no retry, no delivery guarantee, no runtime dependencies. One vendor failing never affects another, and never reaches your call site.

Destinations

  • GA4
  • Meta — not yet
  • LinkedIn — not yet
  • PostHog — not yet
  • X — not yet

Any tool that receives events about what your users do can be a destination — not just ad pixels. Writing your own uses the same contract the built-in ones use: no registry entry, no allow-list.