Skip to content

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.

ts
import { createIdentity } from "@lucerna-dev/identity";

const identity = createIdentity({
  apiKey: "ck_client_prod_…",
  storage: localStorage, // only after consent
});

Options ​

OptionTypeDefaultDescription
apiKeystring— (required)Any key granted people:identify — the publishable client key in browsers, a server or scoped key on servers.
storageIdentityStorageundefinedWhere to persist the user across reloads (structurally localStorage). Nothing is stored without it — consent-gated.
storageKeystring"lucerna:identity"The storage entry name.
baseUrlstringhttps://api.uselucerna.appOverride for the API origin.
syncAnonymousbooleanfalseAlso sync anonymous visitors to People. Off by default to avoid ghost profiles.
onError(error: Error) => voidundefinedCalled when a background sync fails. Errors are never thrown into your code.
fetchFetchLikeglobal fetchInject a transport — handy in tests.

Methods ​

identify(input) ​

Declares who the current user is.

ts
identity.identify({
  userId: "u_42",
  email: "ada@example.com",
  name: "Ada Lovelace",
  traits: { plan: "pro" },
});
FieldTypeNotes
userIdstringRequired. Your app's id — never an email.
emailstring?PII — routed to encrypted People fields, never into traits.
namestring?Same handling as email.
traitsRecord<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:

ts
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):

ts
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.

ts
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 onError and 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. email and name travel only to People; SDKs consuming current() send userId and traits alone.

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.

Lucerna Developer Docs