# Web SDK

@cohorly/web is the browser SDK. It is built on the shared core with localStorage persistence, a fetch/sendBeacon transport (so events survive tab close and navigation), and optional SPA pageview autotracking.

## Install

```bash
pnpm add @cohorly/web
# or: npm install @cohorly/web
```

## Initialize

Call `init()` once at your app entry point. Pass your project `token` so
events route to the right project; `apiHost` defaults to the hosted Cohorly
API.

```ts
import { init } from "@cohorly/web";

const cohorly = init({
  token: "YOUR_PROJECT_TOKEN",
  trackPageviews: true,
});
```

After `init()` has run once, you can import the named `cohorly` singleton
anywhere instead of threading the returned instance through your app:

```ts
import { cohorly } from "@cohorly/web";

cohorly.track("Button Clicked", { button: "checkout" });
```

`init()` is SSR-safe: browser-only APIs (localStorage, `window`,
`sendBeacon`) fall back to in-memory/no-op implementations when
unavailable.

### Options

| Option            | Type    | Notes                                                                      |
| ----------------- | ------- | -------------------------------------------------------------------------- |
| `apiHost`         | string  | Optional. Ingestion API base URL. Defaults to the hosted Cohorly endpoint. |
| `token`           | string  | Project token stamped on every event and request.                          |
| `trackPageviews`  | boolean | Autotrack SPA pageviews.                                                   |
| `flushIntervalMs` | number  | How often the queue auto-flushes.                                          |
| `batchSize`       | number  | Flush once this many events are queued.                                    |
| `superProperties` | object  | Properties merged into every event from the start.                         |
| `debug`           | boolean | Verbose logging.                                                           |

## Track events

`track(event, properties?)` queues an event. The SDK automatically merges in
`$browser`, `$os`, `$current_url`, and screen size, plus `distinct_id`,
`time`, `$insert_id`, and `$lib`.

```ts
cohorly.track("Signed Up", { plan: "pro" });
cohorly.track("Item Added", { sku: "A-100", price: 29 });
```

## Identify

`identify(id)` switches the current user to a known id. The switch (distinct
id, persisted state) is applied synchronously, before any network call, so a
slow or offline request never delays it. On the first call after being
anonymous it then sends an `/alias` request so pre-login and post-login
activity link into one profile; the request is best effort - a failure
neither throws nor blocks, since the server also links the anonymous and
identified ids implicitly from the next event that carries both (see
[Identity](/reference/data-model#identity)).

```ts
await cohorly.identify("user_123");
```

Use `reset()` on logout to generate a fresh anonymous id. This matters on
shared devices: without it, the next person to use the device inherits the
previous person's anonymous history.

```ts
cohorly.reset();
```

## Super properties

Super properties are merged into every subsequent event. Register and
unregister them with `register()` / `unregister()`. Event-level properties
override super properties of the same key.

```ts
cohorly.register({ app_version: "1.2.3", plan: "free" });
cohorly.track("Viewed Page"); // includes app_version and plan
cohorly.unregister("plan");
```

## People profiles

Update the current user's profile through `cohorly.people`. These map to the
`/engage` endpoint.

```ts
await cohorly.people.set({ name: "Ada Lovelace", plan: "pro" });
await cohorly.people.setOnce({ first_seen: Date.now() });
await cohorly.people.increment({ logins: 1 });
await cohorly.people.unset(["temp_flag"]);
await cohorly.people.delete();
```

## Flush and accessors

```ts
await cohorly.flush(); // send the queued batch now
const id = cohorly.getDistinctId();
const anon = cohorly.isAnonymous();
```

The queue is persisted to localStorage, so events survive reloads and are
retried on the next flush if a request fails.

## Keeping the token out of the bundle

Passing `token` to `init()` embeds it in your client JS. If you would rather
not, proxy ingestion server-side with `@cohorly/nextjs`'s
`createCohorlyProxy()` and omit `token` here. See the
[Next.js SDK](/sdks/nextjs).
