> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dataframer.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Browser SDK

> Send user events from your frontend with @dataframer/signals

`@dataframer/signals` is a small browser SDK that sends user/product events to DataFramer and generates the journey id that ties those events to your AI traces. It follows the same `track`/`identify`/`group` shape as Segment, so if your app already uses Segment, this will feel familiar.

## Install

```bash theme={null}
npm install @dataframer/signals
```

## Get a write key

In your DataFramer dashboard: **Profile → Signal Write Keys** → create a key. This key is public-safe: it's meant to sit in frontend code and can only send events in, not read data out.

## Initialize once, at app start

```ts theme={null}
import { DataframerSignals } from '@dataframer/signals';

const signals = new DataframerSignals({
  apiBase: 'https://your-dataframer-api',   // where /api/signals/* lives
  writeKey: 'YOUR_WRITE_KEY',
});
```

## Send events

```ts theme={null}
signals.track('RecommendationCancelled', { itemId: 'abc', reason: 'irrelevant' });
signals.identify('user@acme.com', { plan: 'pro' });   // sets the journey's user
signals.group('acme');                                 // account/org rollup
```

Sending is best-effort: it will never throw an error into your app.

## Journey ids

Every event you send carries a `journey_id` automatically:

* **Default:** a journey id is created for you and stored in a cookie + `localStorage`. It resets after 30 minutes of inactivity.
* **Better, use your own id:** if you already have a natural id (order id, conversation id, ticket id), pin it:

```ts theme={null}
signals.startJourney('order-1234');   // pin your own id, never auto-resets
signals.resetJourney();               // end it, start a fresh one
signals.journeyId;                    // read the current id
```

## CORS: when you need it, and when you don't

The SDK sends events with a normal browser `fetch` call to `apiBase`. Whether you need to touch CORS depends on where your backend lives:

<Tabs>
  <Tab title="Same site: nothing to do">
    If your frontend and your backend are on the same domain (or share a parent domain, like `app.acme.com` and `api.acme.com` with cookies set on `.acme.com`), the journey id rides along as a cookie automatically. **You don't need to configure anything.**
  </Tab>

  <Tab title="Different domains: two steps">
    If your frontend and backend are on unrelated domains (e.g. a SaaS product frontend calling a customer's own API on a different domain), cookies won't reliably cross that boundary. Do two things:

    1. **Tell the SDK to forward the id as a header**, by listing your API's URL:

    ```ts theme={null}
    const signals = new DataframerSignals({
      apiBase: 'https://your-dataframer-api',
      writeKey: 'YOUR_WRITE_KEY',
      propagateHeaderTo: ['https://your-backend'],   // your API's origin
    });
    ```

    This patches `fetch`/`XHR` so any request to `https://your-backend` automatically gets an `X-Journey-Id` header attached.

    2. **Allow that header in your backend's CORS config**, the same way you'd already allow `Authorization` or `Content-Type`. For example, with Django's `django-cors-headers`:

    ```python theme={null}
    from corsheaders.defaults import default_headers

    CORS_ALLOW_HEADERS = list(default_headers) + ['x-journey-id']
    ```

    Without this, the browser's CORS preflight will reject the request before it reaches your server. This is exactly what we had to add for our own dashboard, since our frontend and backend sit on different domains.
  </Tab>
</Tabs>

## Config reference

| Option                | Default | What it does                                                    |
| --------------------- | ------- | --------------------------------------------------------------- |
| `apiBase`             | none    | Base URL where `/api/signals/*` lives                           |
| `writeKey`            | none    | Your public write key                                           |
| `journeyIdleMinutes`  | `30`    | Minutes of inactivity before an auto journey id resets          |
| `disableAutoRotation` | `false` | Never auto-reset the journey id                                 |
| `propagateHeaderTo`   | `[]`    | Origins to attach the `X-Journey-Id` header to (see CORS above) |

## Next steps

<Card title="Server Instrumentation" icon="server" href="/user-signals/server-instrumentation">
  Stamp this same journey id onto your AI traces automatically.
</Card>
