Events.
Two ways to deliver exposures and dif.track() metrics: send them to dif.sh Cloud, or
send them wherever you want yourself. Pick one in dif/config.yaml; dif build compiles the choice into dif/generated/events.ts.
Two modes
The events.mode field in dif/config.yaml is either cloud or custom. dif init asks which one on an interactive terminal, and defaults
to cloud otherwise.
| mode | what it does |
|---|---|
cloud | The default. The SDK posts exposures to <apiUrl>/v1/exposure and dif.track() metrics to <apiUrl>/v1/track, authenticated with a
publishable key. |
custom | The SDK calls handlers you export from dif/events/exposure.ts and dif/events/track.ts. Forward to Amplitude, Mixpanel, Segment, a webhook —
whatever you like. |
Cloud mode Cloud
The built-in path. Connect an existing workspace with dif connect:
$dif connect --key dif_pk_live_… ✓ connected dif/config.yaml to dif.sh Cloud next: run dif build to regenerate the client with your key
That writes the key into the events: block and forces cloud mode — safe to commit,
it's the publishable key, never the secret server key:
events: mode: cloud url: https://cloud.dif.sh key: dif_pk_live_…
A fresh workspace can skip the two-step: dif init --key dif_pk_live_… writes the same
block. Either way, dif build bakes it into dif/generated/events.ts; pass
that straight to dif.init():
import { dif } from "@dif.sh/sdk"; import { events } from "@/dif/generated/events"; dif.init({ events });
The publishable key rides on the generated events object once you've connected — no
environment variable needed. An explicit top-level publishableKey passed to dif.init() overrides it, handy for swapping keys per environment. Without a
publishable key anywhere, cloud events are dropped with a one-time console warning — analytics
never block a render. Server-side tracking via the
server SDK is always cloud.
Custom mode
Mirrors audience resolvers: you export a
default function, dif calls it. exposure fires once per (experiment, user) per session at render time; track fires on every dif.track(...) call. dif init --events custom scaffolds both files.
// dif/events/exposure.ts import type { ExposureEvent } from "@dif.sh/sdk"; export default function exposure(event: ExposureEvent): void { amplitude.track("dif.exposure", { experiment: event.experiment, variant: event.variant, }); }
// dif/events/track.ts import type { MetricEvent } from "@dif.sh/sdk"; export default function track(event: MetricEvent): void { mixpanel.track(event.metric, { value: event.value, ...event.props }); }
Keep handlers non-throwing — analytics must never crash a render. No publishable key is needed in custom mode; delivery is entirely yours.
The generated module
dif build writes dif/generated/events.ts from your config. In custom mode
it imports your handlers:
// dif/generated/events.ts — generated by dif build import exposure from "../events/exposure"; import track from "../events/track"; export const events = { mode: "custom", exposure, track } as const;
In cloud mode it records the URL and, once connected, the key:
export const events = { mode: "cloud", apiUrl: "https://cloud.dif.sh", publishableKey: "dif_pk_live_…" } as const;
Either way the app does the same thing: import events and pass it to dif.init(). dif/generated/ is gitignored by default — it's a build
artifact, not source.
Migrating from exposure:
Earlier versions configured delivery with an exposure: block. That key is now
ignored — the workspace defaults to cloud — and dif validate flags it as W003. Replace it with an events: block: mode: cloud to keep
using dif.sh Cloud, or mode: custom to move your own delivery into dif/events/exposure.ts and dif/events/track.ts.