convex
Convex Guidelines
Function Syntax
ALWAYS use the new function syntax with args, returns, and handler:
import { query } from "./_generated/server";
import { v } from "convex/values";
export const f = query({
args: { name: v.string() },
returns: v.string(),
handler: async (ctx, args) => {
return "Hello " + args.name;
},
});
HTTP Endpoints
import { httpRouter } from "convex/server";
import { httpAction } from "./_generated/server";
const http = httpRouter();
http.route({
path: "/echo",
method: "POST",
handler: httpAction(async (ctx, req) => {
const body = await req.bytes();
return new Response(body, { status: 200 });
}),
});
Validators
| Type | TS Type | Validator | Notes |
|---|---|---|---|
| Id | string | v.id(tableName) |
|
| Null | null | v.null() |
Use instead of undefined |
| Int64 | bigint | v.int64() |
NOT v.bigint() (deprecated) |
| Float64 | number | v.number() |
|
| Boolean | boolean | v.boolean() |
|
| String | string | v.string() |
|
| Bytes | ArrayBuffer | v.bytes() |
|
| Array | Array | v.array(values) |
Max 8192 values |
| Object | Object | v.object({prop: value}) |
Max 1024 entries |
| Record | Record | v.record(keys, values) |
Keys: ASCII, nonempty, no $ or _ prefix |
Discriminated Unions
v.union(
v.object({ kind: v.literal("error"), errorMessage: v.string() }),
v.object({ kind: v.literal("success"), value: v.number() }),
)
Function Registration
- Public:
query,mutation,action- exposed to internet - Internal:
internalQuery,internalMutation,internalAction- only callable by other Convex functions
ALWAYS include argument AND return validators. Use returns: v.null() if no return value.
Function Calling
ctx.runQuery- call query from query/mutation/actionctx.runMutation- call mutation from mutation/actionctx.runAction- call action from action (only for crossing runtimes V8↔Node)
Use FunctionReference (from api or internal objects), NOT the function directly:
// Correct
await ctx.runQuery(api.example.f, { name: "Bob" });
// Wrong
await ctx.runQuery(f, { name: "Bob" });
For same-file calls, add type annotation to avoid circularity:
const result: string = await ctx.runQuery(api.example.f, { name: "Bob" });
Function References
api.path.to.functionName- public functionsinternal.path.to.functionName- internal functions
File-based routing: convex/messages/access.ts → api.messages.access.functionName
Schema
Define in convex/schema.ts:
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
messages: defineTable({
channelId: v.id("channels"),
content: v.string(),
}).index("by_channel", ["channelId"]),
});
System fields auto-added: _id (v.id(tableName)), _creationTime (v.number())
Index naming: include all fields → by_field1_and_field2
Queries
- Do NOT use
filter()- usewithIndex()instead - No
.delete()- collect results, iterate, callctx.db.delete(row._id) .unique()for single document (throws if multiple match)- Default order: ascending
_creationTime
const messages = await ctx.db
.query("messages")
.withIndex("by_channel", (q) => q.eq("channelId", channelId))
.order("desc")
.take(10);
Full Text Search
const messages = await ctx.db
.query("messages")
.withSearchIndex("search_body", (q) =>
q.search("body", "hello hi").eq("channel", "#general"),
)
.take(10);
Pagination
import { paginationOptsValidator } from "convex/server";
export const list = query({
args: { paginationOpts: paginationOptsValidator, author: v.string() },
handler: async (ctx, args) => {
return await ctx.db
.query("messages")
.withIndex("by_author", (q) => q.eq("author", args.author))
.order("desc")
.paginate(args.paginationOpts);
},
});
Returns: { page, isDone, continueCursor }
Mutations
ctx.db.patch(id, fields)- shallow mergectx.db.replace(id, doc)- full replace
Actions
Add "use node"; at top for Node.js modules. Actions have NO ctx.db access.
"use node";
import { action } from "./_generated/server";
export const myAction = action({
args: {},
returns: v.null(),
handler: async (ctx, args) => {
// Use ctx.runQuery/ctx.runMutation for DB access
return null;
},
});
Scheduling
Crons
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";
const crons = cronJobs();
crons.interval("job name", { hours: 2 }, internal.crons.myFunc, {});
export default crons;
Only use crons.interval or crons.cron. NOT crons.hourly/daily/weekly.
File Storage
const url = await ctx.storage.getUrl(storageId); // Returns signed URL or null
// Get metadata via system table
const metadata = await ctx.db.system.get(storageId);
// Returns: { _id, _creationTime, contentType?, sha256, size }
TypeScript
- Use
Id<'tableName'>from./_generated/dataModelfor typed IDs as constfor string literals in unions- Add
@types/nodeto package.json when using Node.js modules
import { Id } from "./_generated/dataModel";
const idToName: Record<Id<"users">, string> = {};
Examples
- Chat app with AI: See chat-app-example.md
More from sebastiaanwouters/dotagents
flyctl
Deploy and manage apps on Fly.io using flyctl CLI. Triggers on: fly deploy, fly.io, flyctl, deploy to fly. Handles launch, deploy, scale, secrets, volumes, databases.
79teacher
Guide learning and deep understanding through proven methodologies (Socratic, Feynman, Problem-Based). Use when user says "help me understand", "teach me", "explain this", "learn about", "socratic", "feynman", "problem-based", "I don't understand", "confused about", "why does", or wants to truly grasp a concept.
77chef
Telegram communication for AI agents. ALL methods are BLOCKING. Use for user interviews, status updates, and feedback collection.
34bitwarden
Retrieves API keys, passwords, secrets from Bitwarden vault using bw CLI. Triggers on missing env variables, missing API keys, missing secrets, "secret not found", "env not set", or "use bw".
29librarian
Use for code research that needs dependency internals, upstream implementation examples, or external prior art. Always delegate to a subagent that investigates with opensrc and web search, then return only distilled findings, versions, paths, and links.
29frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications. Generates creative, polished code that avoids generic AI aesthetics.
28