Function context
The ctx object passed to every query, mutation, and action — and what each kind is allowed to do with it.
Last updated:
Every Lunora function receives a ctx as the first argument of its handler.
ctx is the only way a function reaches anything outside itself: the database,
auth, storage, the scheduler, and any add-on surfaces. The function kind decides
which ctx you get, and the kind constrains what ctx can do. A query cannot
write; an action cannot transact against the database.
import { mutation, query } from "@/lunora/_generated/server";
export const list = query.query(async ({ ctx }) => {
return ctx.db.query("messages").take(50); // read-only
});
export const send = mutation.mutation(async ({ ctx, args }) => {
if (!ctx.auth.userId) throw new Error("must be signed in");
await ctx.db.insert("messages", args); // transactional write
});The three context types
| Context | Function kind | ctx.db | Side effects |
|---|---|---|---|
QueryCtx | query | DatabaseReader | none — pure reads |
MutationCtx | mutation | DatabaseWriter | transactional writes against one shard |
ActionCtx | action | DatabaseWriter | unrestricted — fetch, external APIs, composition |
QueryCtx
Read-only. ctx.db is a DatabaseReader (get, query, normalizeId, plus
the ctx.db.system reader for _scheduled_functions / _storage). Storage
and vectors are present but read-only (ReadOnlyStorage, VectorSearchReader).
A query may compose other queries with ctx.runQuery, observing the same
transaction snapshot. There is no runMutation/runAction, because a query is
not allowed to write.
MutationCtx
Reads plus transactional writes. ctx.db is a DatabaseWriter (insert,
patch, replace, delete on top of the reader surface), running inside one
shard's transactional scope. Adds ctx.scheduler (enqueue deferred work) and
ctx.workflows (start/inspect durable workflows), and upgrades ctx.vectors
to the mutating VectorSearch. ctx.storage stays read-only — R2 writes are a
side effect reserved for actions. A mutation can compose other mutations and
queries (ctx.runMutation, ctx.runQuery), both reusing the same db writer.
ActionCtx
The escape hatch. An action can do anything a Worker can: it gets ctx.fetch
(the standard fetch) for calling external APIs, the full read/write
ctx.storage (store, delete, generateUploadUrl), ctx.scheduler,
ctx.workflows, and the mutating ctx.vectors. It composes other functions of
every kind: ctx.runQuery, ctx.runMutation, ctx.runAction. An action's
ctx.db writes are not part of a single mutation transaction. Do
transactional reads and writes inside a mutation and call it from the action.
Surfaces on every ctx
These are present regardless of kind:
ctx.auth— the authenticated identity.ctx.auth.userIdis the verified user id ornull;ctx.auth.getIdentity()resolves the full claim set. Populated by@lunora/authwhen a session is present.ctx.log— a structured, function-attributed logger (log,info,warn,error,debug). A drop-in forconsole, except each line is tagged with the function path and routed to your observability sink or the dev terminal.ctx.ip— the caller's trustedCF-Connecting-IP, orundefinedfor a live-subscription re-run, a server-initiated dispatch, or non-Cloudflare hosting. Useful as a rate-limit key for anonymous traffic.ctx.now— the wall-clock instant (epoch ms) the function began, captured once so the whole handler sees a single stable value. Read time from here in aquery/mutationinstead ofDate.now(): those handlers must be deterministic (they may be re-run on OCC retry / subscription re-eval), and thenondeterministic_query_mutationadvisor flagsDate.now(). Actions may useDate.now()freely but getctx.nowtoo for parity.
ctx field matrix
| Field | QueryCtx | MutationCtx | ActionCtx |
|---|---|---|---|
db | DatabaseReader | DatabaseWriter | DatabaseWriter* |
auth | ✓ | ✓ | ✓ |
log | ✓ | ✓ | ✓ |
ip | ✓ | ✓ | ✓ |
now | ✓ | ✓ | ✓ |
storage | read-only | read-only | read/write |
vectors | read-only | read/write | read/write |
scheduler | — | ✓ | ✓ |
workflows | — | ✓ | ✓ |
fetch | — | — | ✓ |
runQuery | ✓ | ✓ | ✓ |
runMutation | — | ✓ | ✓ |
runAction | — | — | ✓ |
* An action's ctx.db is not transactional — see above.
Add-on surfaces (codegen-wired)
Opt-in packages attach extra surfaces onto ctx through codegen. When a
project uses one, the generated _generated/server.ts widens the context type
so the surface is typed and present at runtime. Add-ons that wire a ctx
surface include:
@lunora/ai→ctx.ai(action context)@lunora/flags→ctx.flags(every context)@lunora/bindings/analytics→ctx.analytics@lunora/bindings/kv→ctx.kv@lunora/hyperdrive→ctx.sql(action context)@lunora/browser→ctx.browser(action context)@lunora/bindings/images→ctx.images@lunora/container→ctx.containers
The base QueryCtx / MutationCtx / ActionCtx in @lunora/server do not
declare these; they appear only after codegen detects the add-on. Middleware
can also augment ctx for downstream handlers (see
Middleware).