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 likeuserId,fullName,mobileNumbers, andemailAddresses.- The
_luaprefix indicates this is a special, system-provided property. - This data is immutable; any attempts to change it will be silently ignored.
- The
user.*(Mutable): Continue to use the mainuserobject 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.nameAuto Sanitization
Removes sensitive fields automatically
Built-in Methods
update(), save(), send(), and clear() includedMessaging
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:
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)
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.
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:
(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:
Examples:
- Current User
- Specific User (Webhooks/LuaJob)
- Dynamic Jobs (Special Case)
- Email/Phone Lookup
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
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 tonull stores null; only unset removes it.
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 aroundpatch({ 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.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)
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 structuredcontent parts you can render directly.
clear()
Clear all User data for the user represented by this instance. An instance returned byUser.get(target) remains bound to that target.
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.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
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
β
Use Direct Property Access
β Use Direct Property Access
β
Batch Updates
β Batch Updates
Combine multiple updates into one call:
β
Handle Missing Fields
β Handle Missing Fields
Not all fields may be present:
β
Use for Personalization
β Use for Personalization
β
Use save() for Multiple Changes
β Use save() for Multiple Changes
The new
save() method is perfect for multiple changes:β
Use send() for Proactive Notifications
β Use send() for Proactive Notifications
Send messages to users for order updates, alerts, and more:
β οΈ Message Format Requirements
β οΈ Message Format Requirements
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

