Documentation

Type-safe events

Typos in event names silently split your data — signup_completed and signup_complete become two different events. Declare your events once and TypeScript autocompletes names and checks property shapes everywhere you track.

NPM package

Either pass an event map as a generic, or augment the global DtcEventMap interface once — the augmentation types every tracker in your project, including window.__dtc.

Typed events

import { createTracker } from '@didtheyclick/sdk';

type MyEvents = {
	signup_completed: { plan: string };
	purchase_promo: { code: string };
};

const tracker = createTracker<MyEvents>({
	websiteId: 'YOUR_API_KEY',
	endpoint: 'https://www.didthey.click/api/collect'
});

tracker.track('signup_completed', { plan: 'pro' }); // ✅ autocompleted
tracker.track('signup_compleded', {});              // ❌ compile error

The same DtcEventMap augmentation also types createServerTracker from @didtheyclick/sdk/server, so client and server events share one definition.

Declare your events

The tracker lives on window.__dtc. Declare its types once in a .d.ts file anywhere in your project:

Script tag typings

// dtc.d.ts — anywhere in your project's include path
export {};

declare global {
	interface DtcEventMap {
		signup_completed: { plan: string };
		purchase_promo: { code: string };
	}

	interface Window {
		__dtc?: {
			track<K extends keyof DtcEventMap & string>(
				name: K,
				properties?: DtcEventMap[K]
			): void;
			identify(userId: string, traits?: Record<string, string>): void;
			page(): void;
		};
	}
}

Compile-time only

These types exist purely in your editor and build. The tracker accepts any event name at runtime — nothing breaks for untyped callers, and events won't be rejected if the types drift.

Keep values as strings

Event properties are string key/value pairs on the wire. Stick to string values in your event map so the types match what's actually sent.