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

# User

> The current end user's per-agent data record, profile, conversation history, and inbox

`User` reads and writes one data record per end user and agent, exposes the end user's read-only profile, sends messages into their conversation, and reads that conversation back. It's available wherever a current end user exists: tools, dynamic jobs, and trigger-fired turns. A webhook's `execute` and a `LuaJob` have no current end user, so pass an id there; see [execution contexts](/concepts/execution-contexts) for what each context resolves to. For many records per agent, use [`Data`](/reference/sdk/data).

*Verified against lua-cli 3.33.0.*

```ts theme={null}
import { User } from 'lua-cli';
```

## Quick example

```ts theme={null}
import { User } from 'lua-cli';

const user = await User.get();
if (!user) throw new Error('No end user in this context');

await user.patch({ set: { plan: 'pro' }, unset: ['trialEndsAt'] });
await user.send([{ type: 'text', text: `You are on the ${user.plan} plan.` }]);
```

## Methods

### get()

Returns the current end user, an end user by id, or one found by email or phone.

```ts theme={null}
User.get(identifier?: string | UserLookupOptions): Promise<UserDataInstance | null>
```

<ParamField path="identifier" type="string | UserLookupOptions">
  Omit it for the current end user. Pass a user id, or `{ email }` or `{ phone }` to look an end user up. `email` wins when both are set; the phone may include or omit the leading `+`.
</ParamField>

**Returns**

<ResponseField name="user" type="UserDataInstance | null">
  The end user's record and profile. `null` only when an email or phone lookup finds nobody. An id returns an instance even before the end user has a data record; the first write creates it.
</ResponseField>

**Example**

```ts theme={null}
import { User } from 'lua-cli';

const user = await User.get({ email: 'user@example.com' });
if (!user) throw new Error('No user with that email');
await user.update({ routingEnabled: true });
```

**Errors** — throws when called with no identifier in a context that has no current end user, and when the platform rejects the request.

### getChatHistory()

Returns the current end user's conversation with this agent.

```ts theme={null}
User.getChatHistory(): Promise<ChatHistoryMessage[]>
```

**Returns** — an array of [`ChatHistoryMessage`](#chathistorymessage). Only `user` and `assistant` turns are included; each turn's `content` is a list of typed parts. Equivalent to calling `getChatHistory()` on the instance `get()` returns.

**Example**

```ts theme={null}
import { User } from 'lua-cli';

const history = await User.getChatHistory();
const texts = history.flatMap((m) => m.content).filter((part) => part.type === 'text');
const lastText = texts[texts.length - 1]?.text;
```

**Errors** — none beyond network errors.

### Inbox.push()

Files an approval, notice, or connection-fix card in the current end user's inbox. Parameters, receipt, card kinds, and limits are on [`User.Inbox`](/reference/sdk/inbox).

```ts theme={null}
User.Inbox.push(input: InboxPushInput): Promise<InboxPushReceipt>
```

## UserDataInstance

The object `get()` returns. Fields of the data record are readable and writable directly (`user.plan`) and through `user.data`. Direct assignments stay local until `save()`; `update()` and `patch()` write immediately.

<ResponseField name="data" type="Record<string, any>">
  The stored record. `JSON.stringify(user)` and `console.log(user)` print this object only.
</ResponseField>

<ResponseField name="_luaProfile" type="{ userId: string; fullName: string; mobileNumbers: string[]; emailAddresses: string[] }">
  Read-only identity from the platform. Assignments to it are ignored. Empty strings and arrays when the platform sent no profile.
</ResponseField>

### update()

Merges fields into the record on the server and locally.

```ts theme={null}
user.update(data: Record<string, any>): Promise<any>
```

<ParamField path="data" type="Record<string, any>" required>
  Fields to add or replace.
</ParamField>

**Returns** — the record after the write.

**Example**

```ts theme={null}
import { User } from 'lua-cli';

const user = await User.get('user_abc123');
if (!user) throw new Error('User not found');
await user.update({ preferences: { theme: 'dark', digest: 'weekly' } });
```

**Errors** — `Failed to update user data`, with the platform's error as `cause`.

<Note>
  In deployed agents, writing `firstName` or `lastName` also updates `_luaProfile.fullName`. Local runs leave the profile untouched.
</Note>

### patch()

Sets and removes top-level fields in one request.

```ts theme={null}
user.patch(mutation: { set?: Record<string, any>; unset?: string[] }): Promise<any>
```

<ParamField path="mutation.set" type="Record<string, any>">
  Fields to set. A `null` value is stored as `null`; use `unset` to remove a field.
</ParamField>

<ParamField path="mutation.unset" type="string[]">
  Top-level field names to remove.
</ParamField>

**Returns** — the record after the write.

**Example**

```ts theme={null}
import { User } from 'lua-cli';

const user = await User.get();
if (!user) throw new Error('No end user in this context');
await user.patch({ set: { rosterVersion: 12, status: null }, unset: ['repCode'] });
```

**Errors** — `Failed to patch user data`, with the platform's error as `cause`.

### unset()

Removes top-level fields; shorthand for `patch({ unset: fields })`.

```ts theme={null}
user.unset(...fields: string[]): Promise<any>
```

**Example**

```ts theme={null}
import { User } from 'lua-cli';

const user = await User.get();
if (!user) throw new Error('No end user in this context');
await user.unset('repCode', 'legacyAssignment');
```

**Errors** — as `patch()`.

### clear()

Deletes the end user's whole data record for this agent. The profile and the conversation are kept, and a later `get()` reads `_luaProfile` without recreating the record.

```ts theme={null}
user.clear(): Promise<boolean>
```

**Returns** — `true` under `lua test`; deployed agents resolve `{ success: true }` instead. Both are truthy and a failure throws, so don't inspect the value.

**Example**

```ts theme={null}
import { User } from 'lua-cli';

const user = await User.get('user_abc123');
if (!user) throw new Error('User not found');
await user.clear();
```

**Errors** — `Failed to clear user data`, with the platform's error as `cause`.

### save()

Writes the whole local record (`user.data`) to the server.

```ts theme={null}
user.save(): Promise<boolean>
```

**Returns** — `true`.

**Example**

```ts theme={null}
import { User } from 'lua-cli';

const user = await User.get();
if (!user) throw new Error('No end user in this context');
user.onboardingStep = 'verified';
user.verifiedAt = new Date().toISOString();
await user.save();
```

**Errors** — `Failed to save user data`, with the platform's error as `cause`.

### send()

Sends messages into the end user's conversation on the channel they last used: WhatsApp, Messenger, Instagram, a Teams personal chat, MessageBird, or SMS. The web widget always receives a live copy; an end user whose last channel was Slack, Front, iMessage, or RCS can't be reached this way. To pick a channel, reach a contact with no prior conversation, or send a WhatsApp message template, use [`Channels`](/reference/sdk/channels).

```ts theme={null}
user.send(messages: ChatMessage[]): Promise<any>
```

<ParamField path="messages" type="ChatMessage[]" required>
  One or more parts: `{ type: 'text', text }`, `{ type: 'image', image, mediaType }`, or `{ type: 'file', data, mediaType }`. `mediaType` is the MIME type of the payload.
</ParamField>

**Returns** — `true`. In a deployed agent it resolves `true` whether or not the message was delivered, because the runtime discards the dispatch result; under `lua test` a failed send throws. Never branch on the value; confirm delivery in `lua logs`, or use `Channels.send`, which returns a `deliveryId` for `getStatus`.

**Example**

```ts theme={null}
import { User } from 'lua-cli';

const user = await User.get('user_abc123');
if (!user) throw new Error('User not found');
await user.send([
  { type: 'text', text: 'Your order has shipped.' },
  { type: 'text', text: 'Tracking number: 1Z999AA10123456784' },
]);
```

**Errors** — under `lua test`, `Failed to send message`, with the platform's error as `cause`; deployed code never throws from `send()`.

<Note>
  In `lua test`, `send()` and both forms of `getChatHistory()` use the signed-in developer's own conversation with the agent, whichever end user the instance is bound to.
</Note>

### getChatHistory()

Returns the conversation of the end user this instance is bound to.

```ts theme={null}
user.getChatHistory(): Promise<ChatHistoryMessage[]>
```

**Returns** — as the static [`getChatHistory()`](#getchathistory).

**Example**

```ts theme={null}
import { User } from 'lua-cli';

const user = await User.get('user_abc123');
if (!user) throw new Error('User not found');
const turns = (await user.getChatHistory()).length;
```

**Errors** — `Failed to get chat history`, with the platform's error as `cause`.

## Types

### UserLookupOptions

<ResponseField name="email" type="string">
  Email address to look up.
</ResponseField>

<ResponseField name="phone" type="string">
  Phone number to look up, with or without the leading `+`.
</ResponseField>

### ProfileResponse

The platform's profile record behind email and phone lookups. Exported for typing your own helpers; no `User` method returns it directly.

<ResponseField name="id" type="string">The user id.</ResponseField>
<ResponseField name="fullName" type="string">Display name.</ResponseField>
<ResponseField name="mobileNumbers" type="Array<{ number: string; validated: boolean; validatedAt: number }>">Phone numbers with verification state.</ResponseField>
<ResponseField name="emailAddresses" type="Array<{ address: string; validated: boolean; validatedAt: number }>">Email addresses with verification state.</ResponseField>
<ResponseField name="country" type="{ code?: string; name?: string }">Country, when known.</ResponseField>

### ChatHistoryMessage

<ResponseField name="id" type="string">Message id.</ResponseField>
<ResponseField name="role" type="'user' | 'assistant'">Who wrote the turn.</ResponseField>
<ResponseField name="createdAt" type="string">ISO 8601 timestamp.</ResponseField>
<ResponseField name="content" type="ChatHistoryContent[]">The turn's parts.</ResponseField>
<ResponseField name="source" type="'voice' | 'chat'">`voice` for turns mirrored from a voice call. Optional.</ResponseField>
<ResponseField name="model" type="string">Model code that served the turn. Optional.</ResponseField>
<ResponseField name="autoModelRequested" type="boolean">`true` when the turn used the Auto model selector. Optional.</ResponseField>
<ResponseField name="author" type="{ userId: string }">Author, when recorded. Optional.</ResponseField>
<ResponseField name="agent" type="{ agentId: string; name?: string }">Agent that answered. Optional.</ResponseField>
<ResponseField name="audio" type="{ url: string }">Recording of a voice-note user turn. Optional.</ResponseField>

### ChatHistoryContent

One part of a turn. `type` is one of `text`, `reasoning`, `tool`, `image`, `video`, `audio`, `file`, `source-url`, `source-document`, or a `data-lua-*` string; the other fields depend on it.

<ResponseField name="text" type="string">`text` and `reasoning` parts.</ResponseField>
<ResponseField name="image" type="string">`image` parts: URL or payload.</ResponseField>
<ResponseField name="video" type="string">`video` parts: URL or payload.</ResponseField>
<ResponseField name="data" type="string">`audio` and `file` parts: URL or payload.</ResponseField>
<ResponseField name="mediaType" type="string">MIME type of a media part.</ResponseField>
<ResponseField name="toolName" type="string">`tool` parts, with `toolCallId`, `input`, `output`, and `toolState`.</ResponseField>
<ResponseField name="url" type="string">`source-url` parts, with `sourceId` and `title`.</ResponseField>
<ResponseField name="filename" type="string">`source-document` parts, with `sourceId`, `title`, `mediaType`, and `providerMetadata`.</ResponseField>
<ResponseField name="payload" type="unknown">`data-lua-*` parts: the original data object.</ResponseField>

### ChatMessage

`TextMessage | ImageMessage | FileMessage`, the parts `send()` accepts: `{ type: 'text'; text: string }`, `{ type: 'image'; image: string; mediaType: string }`, and `{ type: 'file'; data: string; mediaType: string }`.

## See also

* [`User.Inbox`](/reference/sdk/inbox) — approval and notice cards for the current end user
* [`Data`](/reference/sdk/data) — many records per agent, with filters and semantic search
* [`Channels`](/reference/sdk/channels) — send on a chosen channel or to a cold contact
* [Execution contexts](/concepts/execution-contexts) — which contexts have a current end user
* [Identify users](/build/identify-users) — how-to
