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

# Identity & traits

> Pass an authenticated user id and traits, or let the runtime use a stable anonymous id for visitors who haven't signed in.

FirstFlow trusts the identity you give it your own auth verifies who the user is. The `user` you pass to the provider sets the subject for frequency caps, resume, and persisted rows, and its `traits` drive targeting and branching.

## Identified users

```tsx theme={null}
<FirstflowProvider
  user={{ id: user.id, email: user.email, traits: { plan: user.plan, role: user.role } }}
  experiences={experiences}
  persistence={persistence}
>
```

<ParamField path="id" type="string" required>
  Your stable user identifier. Becomes the `user_id` on persisted runs and the
  key for `once_per_user` caps and resume.
</ParamField>

<ParamField path="traits" type="Record<string, unknown>">
  Arbitrary values for audience targeting and branch conditions, addressed as
  `traits.<key>` in `when` clauses.
</ParamField>

## Anonymous visitors

Omit `user` (or pass no `id`) and the runtime mints a stable per-browser anonymous id, persisted in `localStorage`, so caps and resume work before sign-in.

```tsx theme={null}
<FirstflowProvider experiences={experiences} persistence={persistence}>
```

* The id survives reloads a returning visitor keeps one identity.
* Anonymous users carry `traits.anonymous === true`. Target them with `when: { "traits.anonymous": true }`, or exclude them.
* Read or reuse it directly:

```tsx theme={null}
import { getAnonymousId, isAnonymousId } from "@firstflow/runtime";

const anonId = getAnonymousId();
isAnonymousId(anonId); // true
```

## Targeting with traits

Traits flow into `when` conditions on triggers and step branches:

```json theme={null}
{ "trigger": { "on": "page_load" }, "when": { "traits.plan": "free" } }
```

```json theme={null}
{ "id": "intro", "type": "message", "content": "Hi",
  "branches": [{ "when": { "traits.plan": "pro" }, "goto": "pro_card" }] }
```

To use a server-classified intent inside a flow, pass it as a trait (`traits.intent`) and branch on it. See the [server classifier](/server/analytics).

## Linking to the server SDK

The runtime persists experiences by `user.id` only it has **no conversation id and no backend channel**. To tie a person's experiences to their LLM activity, pass the **same `user.id`** to `FirstflowProvider` and to your server-side calls.

The conversation id (called `sessionId` on the server) is separate: your own chat code owns it and passes it to the [server SDK](/server/overview) to group a transcript. The provider is not involved.
