convex-functions
Writing queries, mutations, actions, and HTTP actions with proper argument validation, error handling, internal functions, and runtime considerations
By waynesutton · 3,014 installs
npx skills add waynesutton/convexskills --skill convex-functions
Source repository · Upstream listing
Convex functions
Every exported function in convex/ uses the object form with args and returns validators. Pick the type by what the handler touches: queries read, mutations write, actions call out.
Pick the function type
Type Database External calls Callable by Use for
query Read No Clients, other functions Reads. Cached and reactive.
mutation Read and write No Clients, other functions Writes. One transaction.
action Only via runQuery and runMutation Yes Clients, scheduler, other actions fetch , third party SDKs, Node APIs
internalQuery , internalMutation , internalAction Same as the public form Same Only other Convex functions Scheduled work, crons, privileged writes
httpAction Only via runQuery and runMutation Yes HTTP requests in convex/http.ts Webhooks, REST endpoints
Default to query or mutation. Reach for an action only when the handler must talk to something outside Convex.
The object form
Declare args and returns on every function. A function that returns nothing declares returns: v.null() and returns null . Hoist a shared document validator when several functions return the same shape.
Reading data
Use ctx.db.get(id) for one document by id. For everything else use withIndex against an index defined in convex/schema.ts . Never call .filter() on a table query; it scans the whole table.
Pick the terminal method by how many documents you expect:
Method Returns Use when
.unique() One doc or null, throws on more than one The index guarantees at most one match
.first() First doc or null You want the newest or oldest match
.take(n) Up to n docs A bounded list such as a recent feed
.collect() Every match The result set is small and stays small
.paginate(opts) A page plus cursor The table is unbounded
Paginated queries take paginationOpts: paginationOptsValidator (from convex/server ) as an argument.
Writing data
Method What it does
ctx.db.insert("tasks", doc) Inserts and returns the new id
ctx.db.patch(id, fields) Shallow merges fields. Throws if the doc is missing
ctx.db.replace(id, doc) Replaces the whole doc. Throws if missing
ctx.db.delete(id) Deletes the doc
Patch directly when you do not need the old value. Reading first widens the window for write conflicts. Make mutations safe to retry.
Internal functions and references
query , mutation , and action are public. Anyone with the deployment URL can call them. Use internalQuery , internalMutation , and internalAction for code that should only run from other Convex code: scheduled jobs, crons, webhook handlers, privileged writes.
Reference functions through the generated objects in ./ generated/api :
api.tasks.get points at a public function in convex/tasks.ts
internal.tasks.markPaid points at an internal function in the same file
Folders map to paths: convex/billing/invoices.ts gives api.billing.invoices.list
Always schedule internal. . Scheduled functions and crons run without a client, so a public reference there skips the auth checks a client call would hit.
Actions and runtime boundaries
Actions have no ctx.db . They read through ctx.runQuery and write through ctx.runMutation . Each call is its own transaction, so keep the count low and do related reads and writes inside one mutation.
fetch works in the default runtime. Add "use node"; as the first line of a file only when an action needs Node built ins or a Node only SDK. A "use node" file can export actions only; queries and mutations go in a separate file.
Doc and Id come from ./ generated/dataModel . The annotation is only needed when the called function lives in the same file.
Errors
Throw ConvexError from convex/values for anything a client should read. Its data reaches the client; a plain Error message is redacted in production. Return null for expected absences such as a lookup that finds nothing. Throw for real failures: not authenticated, not authorized, invalid input.
Thin wrappers
Keep handlers short. Put auth lookups, validation, and business logic in plain async functions that take ctx first, then call them from the wrapper. Plain helpers are testable and shared between queries and mutations without a ctx.runQuery hop.
From a query or mutation, call the helper directly. ctx.runQuery and ctx.runMutation are for actions and component boundaries.
Common mistakes
Mistake Why it breaks Do instead
No returns validator Return shape drifts and client types lie Declare returns , use v.null() for nothing
.filter() on a table query Full table scan Add an index, use withIndex
ctx.db inside an action Actions have no database handle ctx.runQuery and ctx.runMutation
fetch inside a query or mutation Transactions must be deterministic Move it to an action
Scheduling api. Runs public code without a client, skips auth Schedule internal.
"use node" in a file with queries Bundler rejects the file Split actions into their own file
Date.now() in a query Breaks caching and reactivity Pass time as an arg or store a status field
Many runQuery calls from one action Each is a separate transaction, races appear One mutation that does the related work
Plain Error for user messages Message is hidden in production ConvexError
Missing await on ctx.db or scheduler Write may not commit Await every ctx call
Checklist
[ ] Object form with args and returns on every exported function
[ ] returns: v.null() and return null when there is nothing to return
[ ] Reads use ctx.db.get(id) or withIndex , never .filter()
[ ] Unbounded tables use .paginate() or .take(n) , not .collect()
[ ] Mutations patch directly and are safe to retry
[ ] Scheduled and cron targets are internal.
[ ] Actions never touch ctx.db
[ ] "use node" only in files that export actions and need Node
[ ] Same file runQuery and runMutation results have a type annotation
[ ] Client visible errors are ConvexError
[ ] Every ctx. promise is awaited
Docs
https://docs.convex.dev/llms.txt
https://docs.convex.dev/functions
https://docs.convex.dev/functions/validation
https://docs.convex.dev/functions/actions
https://docs.convex.dev/functions/error handling