Skip to content
trakoo
Esc
navigateopen⌘Jpreview
On this page

Quick Start

Install trakoo, define a typed event registry, and send events from client and server instances.

This guide starts with a local console provider, then switches to PostHog and adds server tracking. The same registry drives every instance.

1. Install

pnpm install trakoo

See Installation for npm, yarn, bun, and the provider SDK matrix.

2. Define your events

The object keys organize your source. The name values are the wire names accepted by track() and sent to providers.

import { defineEvents, noProperties, typed } from 'trakoo';

export const appEvents = defineEvents({
  buttonClicked: {
    name: 'button_clicked',
    category: 'engagement',
    properties: typed<{
      buttonId: string;
      location: 'hero' | 'nav' | 'pricing';
    }>()
  },
  userSignedUp: {
    name: 'user_signed_up',
    category: 'user',
    properties: typed<{
      email: string;
      plan: 'free' | 'pro' | 'enterprise';
      referralSource?: string;
    }>()
  },
  sessionStarted: {
    name: 'session_started',
    category: 'user',
    properties: noProperties()
  }
});

typed<T>() is the primary, validator-free API. noProperties() makes the properties argument illegal, so the last event is tracked as analytics.track('session_started').

3. See it work locally

import {
  BaseAnalyticsProvider,
  createClientAnalytics,
  type BaseEvent,
  type EventContext
} from 'trakoo/client';
import { appEvents } from './events';

class ConsoleProvider extends BaseAnalyticsProvider {
  name = 'ConsoleProvider';

  initialize() {}

  track(event: BaseEvent, context?: EventContext) {
    console.log('tracked', { event, context });
  }

  identify(userId: string, traits?: Record<string, unknown>) {
    console.log('identified', { userId, traits });
  }

  pageView(properties?: Record<string, unknown>) {
    console.log('page view', properties);
  }

  reset() {}
}

export const analytics = createClientAnalytics({
  events: appEvents,
  providers: [new ConsoleProvider({ debug: true })],
  debug: import.meta.env.DEV
});

Each factory call creates a fresh instance. This module owns the instance; components import it from your module rather than relying on a trakoo singleton or global helper. Client initialization begins in the background, and track() waits for it when necessary.

4. Track from your UI

If you mistype the wire name or omit location, TypeScript flags the call.

import { analytics } from '@/lib/analytics';

export function SignupButton() {
  return (
    <button
      onClick={() =>
        void analytics
          .track('button_clicked', {
            buttonId: 'signup-cta',
            location: 'hero'
          })
          .catch((error) => {
            console.error('Analytics tracking failed:', error);
          })
      }
    >
      Sign up
    </button>
  );
}
<script lang="ts">
  import { analytics } from '$lib/analytics';

  function trackSignup() {
    void analytics
      .track('button_clicked', {
        buttonId: 'signup-cta',
        location: 'hero'
      })
      .catch((error) => {
        console.error('Analytics tracking failed:', error);
      });
  }
</script>

<button on:click={trackSignup}>Sign up</button>
import { analytics } from './lib/analytics';

document.querySelector('#signup')?.addEventListener('click', () => {
  void analytics
    .track('button_clicked', {
      buttonId: 'signup-cta',
      location: 'hero'
    })
    .catch((error) => {
      console.error('Analytics tracking failed:', error);
    });
});

The console provider receives an event whose action is button_clicked and whose properties match the value you passed.

5. Connect a real provider

pnpm install posthog-js
import { createClientAnalytics } from 'trakoo/client';
import { PostHogClientProvider } from 'trakoo/providers/client';
import { appEvents } from './events';

export const analytics = createClientAnalytics({
  events: appEvents,
  providers: [
    new PostHogClientProvider({
      token: import.meta.env.VITE_POSTHOG_KEY,
      api_host: import.meta.env.VITE_POSTHOG_HOST
    })
  ],
  debug: import.meta.env.DEV
});

Your components do not change.

6. Identify users

Client analytics is stateful. identify() sets the current user until reset().

analytics.identify('user_123', {
  email: 'ada@example.com',
  plan: 'pro'
});

await analytics.track('user_signed_up', {
  email: 'ada@example.com',
  plan: 'pro',
  referralSource: 'homepage'
});

See Identifying Users to type custom traits.

7. Track critical events on the server

import { createServerAnalytics } from 'trakoo/server';
import { PostHogServerProvider } from 'trakoo/providers/server';
import { appEvents } from './events';

export function createRequestAnalytics() {
  return createServerAnalytics({
    events: appEvents,
    providers: [
      new PostHogServerProvider({ apiKey: process.env.POSTHOG_API_KEY! })
    ]
  });
}
import { createRequestAnalytics } from '@/lib/server-analytics';

export async function POST(request: Request) {
  const user = await createUser(await request.json());
  const analytics = createRequestAnalytics();

  try {
    await analytics.track('user_signed_up', {
      email: user.email,
      plan: user.plan
    }, {
      userId: user.id,
      user: { email: user.email, traits: { plan: user.plan } }
    });

    return Response.json({ ok: true });
  } finally {
    await analytics.shutdown();
  }
}

Runtime validation is optional

typed<T>() does not validate individual fields at runtime. For untrusted input, pass a Standard Schema-compatible validator directly. Standard Schema is an interface, not a required validation runtime; libraries such as Zod implement it.

import { defineEvents } from 'trakoo';
import { z } from 'zod';

export const checkoutEvents = defineEvents({
  orderCompleted: {
    name: 'order_completed',
    category: 'conversion',
    properties: z.object({
      orderId: z.string(),
      amount: z.coerce.number().positive()
    })
  }
});

The schema input controls accepted call-site values; its validated output is what providers receive. Read Events for failure policy and ordering details.

Send to more than one provider

import {
  BentoClientProvider,
  PostHogClientProvider,
  VisitorsClientProvider
} from 'trakoo/providers/client';

export const analytics = createClientAnalytics({
  events: appEvents,
  providers: [
    new PostHogClientProvider({ token: import.meta.env.VITE_POSTHOG_KEY }),
    {
      provider: new BentoClientProvider({
        siteUuid: import.meta.env.VITE_BENTO_SITE_UUID
      }),
      methods: ['identify', 'track']
    },
    new VisitorsClientProvider({ token: import.meta.env.VITE_VISITORS_TOKEN })
  ]
});

Next steps

Was this page helpful?