Claude Code for Convex — Workflow Guide
The Setup
You are building a reactive backend with Convex where mutations, queries, and actions automatically sync to connected clients. Claude Code can scaffold Convex functions, manage schema definitions, and wire up real-time subscriptions, but it needs explicit guidance about Convex’s unique server function model and its distinction from traditional REST APIs.
What Claude Code Gets Wrong By Default
-
Generates REST endpoints instead of Convex functions. Claude defaults to Express-style route handlers when you ask for backend logic. Convex uses exported function declarations with
mutation(),query(), andaction()wrappers — not HTTP handlers. -
Uses raw database calls instead of the Convex query builder. Claude tries to write SQL or Prisma-style queries. Convex uses
ctx.db.query("tableName").filter(...)with its own chainable API that does not support raw SQL. -
Misses the
vvalidator import. Convex requires argument validation using its built-invmodule fromconvex/values. Claude often skips validation entirely or imports Zod, which does not work in Convex server functions. -
Puts server logic in the wrong directory. Convex functions must live in the
convex/directory. Claude frequently creates files insrc/api/orserver/which Convex ignores completely.
The CLAUDE.md Configuration
# Convex Backend Project
## Architecture
- Backend: Convex (convex/ directory contains all server functions)
- Frontend: React with convex/react hooks
- Schema: convex/schema.ts defines all tables
- Functions: convex/*.ts files export query(), mutation(), action()
## Convex Rules
- All server functions must be in convex/ directory
- Import validators from "convex/values" using { v }
- Use ctx.db for database operations, never raw SQL
- Queries are reactive — no manual refetch needed
- Mutations must be deterministic (no Date.now(), use ctx.scheduler)
- Actions can call external APIs but cannot write to DB directly
- Use internal functions with internalMutation for action DB writes
## Conventions
- One file per domain (convex/users.ts, convex/messages.ts)
- Export handler name matches the function purpose (getUser, createMessage)
- Always validate arguments with args: { field: v.string() }
- Never import Node.js modules in query/mutation files
- Use convex/http.ts for any HTTP endpoint needs
Workflow Example
You want to add a new “projects” table with CRUD operations. Prompt Claude Code:
“Create a projects table in the Convex schema with name, description, and ownerId fields. Then create query and mutation functions for listing projects by owner and creating new projects.”
Claude Code should produce convex/schema.ts updates with the table definition using defineTable(), plus a convex/projects.ts file exporting list as a query() and create as a mutation(), both with proper v validators on their arguments.
Common Pitfalls
-
Scheduling in mutations. Claude tries to use
setTimeoutinside mutations. Convex mutations cannot use timers — usectx.scheduler.runAfter()to schedule delayed work. -
Importing between function types. Claude imports queries inside mutations for reuse. Convex does not allow calling queries from mutations directly. Use
ctx.dbcalls in both, or extract shared logic into plain helper functions. -
File upload handling. Claude generates multipart form parsing code. Convex uses
storage.generateUploadUrl()on the client side andstorage.getUrl()for retrieval — there is no server-side file parsing.