ctx.user in handlers and
bot.user(userId) anywhere else.
Scope. Keyed by the canonical sender (ctx.sender, the provider-resolved
senderId), so it is shared across every chat that person writes from (DM
and groups) and survives conversations and restarts (with a persistent
store). It lives inside the bot’s store namespace (tenant / environment /
bot / provider / connection), so two bots sharing one backend never see each
other’s profiles.
Namespaces. ctx.user is the 'default' namespace; ctx.user.ns('game')
returns the same API scoped to 'game'. Namespace names are flat and
absolute (ctx.user.ns('a').ns('b') is namespace 'b'). Use one per
feature/plugin to avoid key collisions.
Paths. Every method takes a dot path: 'name', 'address.city',
'items.0'. The FIRST segment is the top-level key, which is the unit the
store persists atomically; deeper segments are resolved inside its JSON value
(array elements by index). Keys cannot contain dots; __proto__,
prototype, constructor, empty segments and control characters are
rejected with UserDataError('INVALID_PATH').
Values are JSON data: they are deep-copied through JSON on write (a
Date becomes its ISO string, undefined members vanish) and every read
returns a fresh copy. BigInt, functions, symbols and cycles reject with
UserDataError('INVALID_VALUE'). Writing undefined deletes the path.
Concurrency. The dispatcher’s per-chat FIFO does NOT serialise one user
across several chats (the same person can be handled in a group and in a DM
at the same time), so the profile serialises writes itself: inside one
process, every write of a user (set / update / delete / clear and the
bot’s subject erasure) runs in call order, one at a time. A top-level
set/delete is a single atomic store write; a nested write is a
read-modify-write of its top-level key. Across several bot replicas sharing a
store, top-level writes stay atomic (last writer wins) but two replicas
writing nested paths of the SAME top-level key concurrently can lose one
update — keep such keys owned by one replica or write whole top-level values.
Reads never wait for writes; await your writes before reading them back.
Example
Properties
userId
ctx.sender).
namespace
'default' for ctx.user).
Methods
get()
path, or undefined when missing or expired. T is an
unchecked convenience cast — validate untrusted shapes yourself.
Type Parameters
Parameters
Returns
Promise<T | undefined>
all()
{} when empty).
Returns
Promise<Record<string, unknown>>
set()
value at path (see UserSetOptions.ttl for expiry). undefined deletes.
Parameters
Returns
Promise<void>
update()
values is a PATH
({ name: 'Juan', 'address.city': 'Bogotá' }), applied in object key
order like set; keys not mentioned are left alone. Paths, values,
ttl and array indexes (against the stored values) are validated before
anything is written, so an invalid batch writes nothing. All paths under
one top-level key become a single store write; the batch as a whole is not
atomic: each top-level key is its own write, so if the STORE fails one of
them the others may already be persisted. The call rejects with the first
store error only after every write of the batch has settled, so the next
write (or erasure) of this user never overlaps it.
Parameters
Returns
Promise<void>
delete()
path (a top-level key or a nested member). Missing paths are a no-op.
Parameters
Returns
Promise<void>
clear()
Returns
Promise<void>
ns()
name. Throws UserDataError('INVALID_NAMESPACE') synchronously.
Parameters
Returns
UserData