— Browser SDK

Browser SDK (sdk-js)

The thinnest path to capturing events, errors, and sessions in any browser-based JavaScript app.

Install

pnpm add @emit-vision/sdk-js
npm install @emit-vision/sdk-js

Initialization

Call init() as early as possible — before the rest of your app starts emitting events.

import { init } from "@emit-vision/sdk-js";

init({
  apiKey: "evk_your_ingest_key",
  environment: "production",
  release: "[email protected]",
  sessionId: crypto.randomUUID(),
  autoCapture: {
    errors: true,
    unhandledRejections: true,
  },
  flushIntervalMs: 5000,
  batchSize: 20,
});

Key options:

  • apiKey — your ingest token (starts with evk_), safe to include in browser bundles.
  • environment — labels telemetry so you can filter by development, staging, or production.
  • release — tags events with the app version or git SHA.
  • sessionId — ties a set of actions together for one browser session.
  • autoCapture — controls whether unhandled errors and promise rejections are automatically recorded.
  • flushIntervalMs — automatic batch flush cadence in milliseconds.
  • batchSize — queue size that triggers an immediate flush.

Capturing events

Use captureEvent(name, properties, options?) for product actions. Keep names in snake_case with a verb_noun pattern.

import { captureEvent } from "@emit-vision/sdk-js";

captureEvent(
  "user_signed_up",
  { plan: "pro", source: "pricing_page" },
  {
    tags: { feature_flag: "new_checkout", ab_test_variant: "b" },
    context: { route: "/signup" },
  },
);

Use tags for values you expect to filter on frequently, especially high-cardinality metadata like feature flags and experiment variants.

Identifying users

Call identify() after login or sign-up so later events include user context.

import { identify } from "@emit-vision/sdk-js";

identify("user_123", {
  email: "[email protected]",
  username: "myusername",
});

Do not send passwords, session tokens, raw auth headers, or any other secrets. User context is attached to subsequent events until you change it again.

Capturing errors

Use captureError() when you are already inside a try/catch and want to attach context. Leave autoCapture enabled for truly unhandled failures.

import { captureError } from "@emit-vision/sdk-js";

try {
  await saveSettings();
} catch (error) {
  captureError(
    error instanceof Error ? error : new Error("save settings failed"),
    { context: { route: "/settings", action: "save_settings" } },
  );
}

Flushing

The SDK batches automatically, but call flush() when timing matters:

import { captureEvent, flush } from "@emit-vision/sdk-js";

captureEvent("checkout_submitted", { plan: "pro" });
await flush();
window.location.assign("/thanks");

Useful before SPA navigation, on logout, or after critical form submissions.

Common mistakes

  • Calling init() inside a component render path — this re-initializes the SDK repeatedly and resets queued telemetry. Call it once at app entry (e.g., main.ts).
  • Sending PII in event properties— avoid tokens, passwords, and free-form user input you don't control.
  • Navigating without flushing — call await flush() before key navigation so the last action is not lost.
  • Passing a plain string to captureError() — wrap it in new Error(message) to preserve a stack trace.