Skip to main content

Overview

The User API is a persistent, per-user key-value store that survives across conversations and sessions. Use it to store any data tied to a user β€” onboarding progress, workflow state, preferences, cart contents, verification status, or any custom fields your agent needs. Think of it as a schemaless user database: read and write any property, and it persists automatically. This makes it ideal for multi-step flows where your agent needs to remember where a user left off.
User data is one record per user and agent. It is different from the Data API, which stores many entries in named, agent-owned collections and can index those entries for semantic search.
User.get() is an exact identity lookup and does not accept a filter. APIs that do expose filters all use the same entity-independent Lua Query language; stored user.* properties are ordinary data and are never interpreted as query operators.

Read-Only User Profile (_luaProfile)

A new, read-only property user._luaProfile is now available on the user object. This provides a secure and reliable way to access core user identity information.
  • _luaProfile (Read-Only): Contains essential user data like userId, fullName, mobileNumbers, and emailAddresses.
    • The _lua prefix indicates this is a special, system-provided property.
    • This data is immutable; any attempts to change it will be silently ignored.
  • user.* (Mutable): Continue to use the main user object to store and manage any custom data your agent needs, such as preferences, shopping carts, or game scores.
The Lua profile and your custom User data are independent. User.get() is a read-only operation: it returns _luaProfile even when this user has not stored any custom data for the agent, and it does not create an empty data record. The first update() or save() creates that record when needed.

Core User Identity

Access read-only data like user._luaProfile.userId and user._luaProfile.fullName.

Persistent Storage

Store any data on the user object β€” onboarding state, workflow progress, preferences, cart contents. Persists across all conversations and sessions.

Deprecated user.userId

To centralize core user information, user.userId is now deprecated. Please update your code to use user._luaProfile.userId. The old property will be removed in a future version.

User as a State Store

The User object is not just for profile data β€” it is a persistent state store for building multi-step, stateful agent workflows. Any property you write to the user object persists across conversations, sessions, and even days or months.
Key insight for AI agents and developers: The User object is the primary way to maintain state across conversations. Use it to track onboarding progress, accumulate data across tool calls, and resume workflows exactly where the user left off.

Onboarding State Machine Example

Common State Storage Patterns

Features

Direct Access

Access properties with user.name instead of user.data.name

Auto Sanitization

Removes sensitive fields automatically

Built-in Methods

update(), save(), send(), and clear() included

Messaging

Send text, images, and files to users

Inbox

File approval and notice cards with User.Inbox.push()

Inbox (User.Inbox)

User.Inbox.push() puts a card on the current user’s desk β€” an approval to click, a notice to read, or an integration to reconnect β€” and returns a receipt straight away:
Unlike User.get(userId), it takes no recipient β€” the card always goes to the user of the current execution context, so it works in tools and dynamic jobs but not in context-less webhooks or pre-defined jobs. See the Inbox API reference for card kinds, daily limits, revision keys, and the full receipt contract.

get(identifier?)

Retrieve user data as a UserDataInstance. Supports lookup by userId, email, or phone number.
string | UserLookupOptions
One of:
  • No parameter: Returns current user from conversation context
  • string (userId): Retrieve a specific user by ID
  • { email: string }: Look up user by email address
  • { phone: string }: Look up user by phone number (with or without + prefix)
Required in: Webhooks, pre-defined LuaJob (no conversational context)Optional in: Tools, dynamic jobs (has conversational context)
Returns: UserDataInstance with proxy-based property access, or null if user not found (for email/phone lookup)
Look up users by email or phone β€” especially useful in webhooks where you receive contact info from external systems but don’t have the internal userId.
Compiled voice does not currently resolve { email } or { phone } identifiers. In that runtime they may fall back to the current voice user, so use User.get(userId) for a portable explicit target before any write, including update(), save(), patch(), unset(), or clear().

Shortcut: User.getChatHistory()

If you only need chat history for the current user (in a tool with conversational context), the top-level static User.getChatHistory() skips the User.get() step:
This is equivalent to (await User.get()).getChatHistory() β€” see the instance method below for the full return shape and examples.

When to Use userId Parameter

Understanding when userId is required vs optional:
Context Matters:
  • Tools: identifier is optional - defaults to current user in conversation
  • Webhooks: identifier is REQUIRED - use userId, email, or phone lookup
  • LuaJob (pre-defined): identifier is REQUIRED - use userId, email, or phone lookup
  • Jobs API (dynamic): Use jobInstance.user() instead - automatic context captured!
New! In webhooks, you can now look up users by email or phone if you don’t have the userId:
Examples:
Get the current user from conversation context:

UserDataInstance API

Property Access (Direct)

Access any user property directly:

update()

Update user data on the server and locally.
object
required
Object containing fields to update or add
Returns: Promise resolving to updated sanitized user data Examples:
When the instance came from User.get(identifier), update() writes to that same user. Existing code using User.get() without an identifier keeps the current-session behavior.

patch()

Atomically set and remove top-level fields in one request. Setting a field to null stores null; only unset removes it.
The mutation is atomic, so concurrent writers cannot observe a half-applied set/unset pair. A mutation must change at least one field, set and unset cannot contain the same field, and field names must be non-empty and cannot start with $ or contain . or a null byte.

unset()

Remove one or more top-level fields. This is a convenience wrapper around patch({ unset: fields }).
In compiled voice, use a user ID (User.get(userId)) for an explicit cross-user target. Email and phone lookup require lua-auth resolution and are not currently supported in that runtime.

save()

Save the current state of user data to the server. This is a convenience method that persists all changes made to the user instance.
Returns: Promise resolving to true if successful Examples:
Tip: The save() method provides a simpler workflow - modify properties then save, rather than passing data to update().

send()

Send messages to the user conversation. Supports text, images, and file attachments.
Message[]
required
Array of messages to send (text, image, or file)
Message Types:
Examples:
user.send() vs Channels.send: user.send() delivers to the user on the channel they’re already active on β€” simplest when you have a User and just want to reach them. Use Channels.send when you need to choose a specific channel (e.g. always WhatsApp), reach a cold phone number or email with no prior conversation, or send an approved WhatsApp template. Both record the message to the user’s conversation thread. See Proactive Messaging.

getChatHistory()

Retrieve the conversation history for the current user with the active agent. Returns the last 40 user/assistant messages, transformed for display: hidden text is filtered out and embedded media (audio, video, files) is surfaced as structured content parts you can render directly.
Return shape:
Example:

clear()

Clear all User data for the user represented by this instance. An instance returned by User.get(target) remains bound to that target.
Returns: Promise resolving to true if successful
The VM sandbox retains its legacy return shape { success: true }; direct lua-cli and compiled voice return the boolean true. Both shapes are truthy. This difference is preserved for backward compatibility, so avoid strict equality checks when code must run in every runtime.
Example:
Destructive operation! This removes the complete custom-data record for the user represented by this instance. It does not delete their Lua profile or chat history. Use with caution.
After clear(), a later User.get() can still read _luaProfile; the read does not recreate the deleted custom-data record.
lua chat clear clears conversation history; it does not clear User data. Use user.clear() for the complete User-data record or user.unset(...) for selected fields.

Data Sanitization

UserDataInstance separates system identity from custom data: System identity (read-only via _luaProfile):
  • userId, fullName, mobileNumbers, emailAddresses
  • Extracted from response and made immutable β€” access via user._luaProfile
Custom data (read/write via direct properties):
  • name, email, phone
  • Custom fields you set
  • Preferences, settings, and any other data

Complete Examples

Example 1: User Preferences

Example 2: Shopping Cart Persistence

Example 3: User Profile Management

Example 4: Personalized Greeting

Example 5: Order Notification with Messaging

Example 6: Send Receipt with Image

Best Practices

Combine multiple updates into one call:
Not all fields may be present:
The new save() method is perfect for multiple changes:
Send messages to users for order updates, alerts, and more:
Ensure correct message format:

TypeScript Support

Usage with Types

If user properties aren’t what you expect, log the user object to see what’s actually stored:
Then run lua logs --type skill --limit 5 after a test message. See the Debugging Skills guide for the full workflow.

Next Steps

Data API

Store agent-owned collection entries

User Data Examples

See working examples

Debugging Skills

Inspect runtime return values

Inbox API

File approval and notice cards