Reference
Complete API for @lucerna-dev/identity. If you're new, start with the quickstart.
createIdentity(options)
Creates an identity. There is no network call at construction — syncing happens as the identity changes.
import { createIdentity } from "@lucerna-dev/identity";
const identity = createIdentity({
apiKey: "ck_client_prod_…",
storage: localStorage, // only after consent
});Options
| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | — (required) | Any key granted people:identify — the publishable client key in browsers, a server or scoped key on servers. |
storage | IdentityStorage | undefined | Where to persist the user across reloads (structurally localStorage). Nothing is stored without it — consent-gated. |
storageKey | string | "lucerna:identity" | The storage entry name. |
baseUrl | string | https://api.uselucerna.app | Override for the API origin. |
syncAnonymous | boolean | false | Also sync anonymous visitors to People. Off by default to avoid ghost profiles. |
onError | (error: Error) => void | undefined | Called when a background sync fails. Errors are never thrown into your code. |
fetch | FetchLike | global fetch | Inject a transport — handy in tests. |
Methods
identify(input)
Declares who the current user is.
identity.identify({
userId: "u_42",
email: "ada@example.com",
name: "Ada Lovelace",
traits: { plan: "pro" },
});| Field | Type | Notes |
|---|---|---|
userId | string | Required. Your app's id — never an email. |
email | string? | PII — routed to encrypted People fields, never into traits. |
name | string? | Same handling as email. |
traits | Record<string, string | number | boolean>? | Merged over existing traits. Values are stored as strings. |
Calling identify again with the same userId merges the new fields over what's already known. A different userId (or a call while anonymous) starts clean — traits never leak from one user to the next.
trait(key, value) / traits(values)
Update what's true about the user:
identity.trait("plan", "pro");
identity.trait("beta", null); // null deletes the trait
identity.traits({ plan: "team", seats: 5 });Values are stringified (true → "true", 5 → "5"); null deletes.
current()
Returns the IdentityUser snapshot every SDK accepts (a defensive copy — mutating it changes nothing):
interface IdentityUser {
userId: string; // "u_42", or "anon_…" until identify()
anonymous: boolean;
email?: string; // PII — SDKs strip these before any request
name?: string;
traits: Record<string, string>;
}onChange(listener)
Fires on every change to the user; returns an unsubscribe function. This is how @lucerna-dev/gates-browser refetches decisions when the user changes.
const stop = identity.onChange((user) => console.log(user.userId));
stop();reset()
Call on logout. Clears traits, mints a fresh anonymous id, and overwrites any persisted state — two people on one device are never linked.
Syncing to People
Every identify(), trait change and reset upserts the person to People, authenticated by your apiKey. This is the package's only wire — there is no second, hidden endpoint.
- Batched — bursts of changes in one tick coalesce into one request.
- Deduplicated — a payload identical to the last successful sync isn't resent.
- Fire-and-forget — never throws, never blocks. A failed sync reports through
onErrorand retries on the next change. - Anonymous visitors are not synced unless
syncAnonymous: true.
Guarantees
- Identity never throws into app code. Storage failures (privacy mode, quota, corrupt JSON) fall back to in-memory; listener errors are contained; sync errors go to
onError. - PII stays out of targeting.
emailandnametravel only to People; SDKs consumingcurrent()senduserIdandtraitsalone.
One identity per request on servers
In the browser, one shared identity is right — there's one user. On a server, create the identity inside the request handler: a module-level identity would target every request as the same user.