# concepts v0.1

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.

modewhat it does
cloudThe default. The SDK posts exposures to <apiUrl>/v1/exposure and dif.track() metrics to <apiUrl>/v1/track, authenticated with a publishable key.
customThe 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.