Convex usage rules
Rules for writing Convex-style code that runs correctly on Nimbus. They apply equally to humans and coding agents; paste this page into an agent’s context when it writes functions for a Nimbus backend.
Function registration
Section titled “Function registration”-
Always use the object syntax with
argsandhandler:import { v } from "convex/values";import { query } from "./_generated/server";export const getMessage = query({args: { id: v.id("messages") },handler: async (ctx, { id }) => await ctx.db.get(id),}); -
Always declare argument validators, even when
argsis empty (args: {}). -
Add a
returns:validator where practical; codegen reads it to produce precise client-facing types in_generated/api.ts. -
Use
internalQuery,internalMutation, andinternalActionfor functions that should never be callable from clients. -
Import registrars from
./_generated/server, values fromconvex/values, and function references from./_generated/api.
Function calling
Section titled “Function calling”ctx.runQuery,ctx.runMutation, andctx.runActionexist only in actions and HTTP actions. Queries and mutations cannot call other functions — share logic through plain TypeScript helper functions instead.- Pass function references (
api.messages.list,internal.messages.purge), never the function value itself. - Actions have no
ctx.db. Read and write throughctx.runQueryandctx.runMutation.
Validators
Section titled “Validators”- The supported set is
v.any,v.null,v.string,v.number,v.boolean,v.id,v.literal,v.array,v.object,v.optional, andv.union. Do not usev.int64,v.bytes,v.record, orv.float64. - Use
v.id("tableName")for document IDs, notv.string(). - Use
v.optional(...)for optional fields andv.union(v.literal(...), ...)for enumerations.
Database access
Section titled “Database access”-
ctx.db.get,ctx.db.patch, andctx.db.deletetake a document ID as their first argument — never a table name:const task = await ctx.db.get(taskId);await ctx.db.patch(taskId, { completed: true });await ctx.db.delete(taskId); -
ctx.db.insert(table, value)takes the table name first. -
There is no
ctx.db.replace. Update fields withctx.db.patch. -
Select rows through an index, not a filter. Define the index in the schema and name it after its fields:
// schema: defineTable({...}).index("by_author", ["author"])const rows = await ctx.db.query("messages").withIndex("by_author", (q) => q.eq("author", author)).collect(); -
Bound result sizes: use
.take(n)or pagination rather than unbounded.collect()on large tables.
Pagination
Section titled “Pagination”- Validate pagination arguments with
paginationOptsValidatorfromconvex/serverand pass them to.paginate(...). The result haspage,isDone, andcontinueCursor. - For React’s
usePaginatedQuery, register the function withpaginatedQuery(from./_generated/server) instead ofquery.
Schema
Section titled “Schema”convex/schema.tsmustexport default defineSchema({...}).defineTabletakes an object of field validators.- Every index field must exist in the table definition; index names must be unique within a table.
- Do not declare
_id,_creationTime, or_updateTime— system fields are automatic.
HTTP actions
Section titled “HTTP actions”- Define routes only in
convex/http.ts, andexport defaulta router initialized withhttpRouter(). - Each route declares exactly one of
pathorpathPrefix, starting with/. - Handlers are
httpActionfunctions; they follow action rules (noctx.db).
Scheduling
Section titled “Scheduling”-
Only mutations can be scheduled. To run work after a mutation commits, schedule an internal mutation:
await ctx.scheduler.runAfter(0, internal.messages.cleanup, { id }); -
runAftertakes a delay in milliseconds;runAttakes a timestamp in milliseconds.
Node runtime
Section titled “Node runtime”- Put
"use node"at the top of a module to run it on the Node runtime. Such modules may contain only actions. - Declare packages that must stay external to the bundle in
convex.jsonundernode.externalPackages. fsandnode:fsstyle builtin specifiers are interchangeable.