Claude Code for Convex — Workflow Guide

Written by Michael Lip · Solo founder of Zovo · $400K+ on Upwork · 100% JSS Join 50+ builders · More at zovo.one

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

  1. 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(), and action() wrappers — not HTTP handlers.

  2. 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.

  3. Misses the v validator import. Convex requires argument validation using its built-in v module from convex/values. Claude often skips validation entirely or imports Zod, which does not work in Convex server functions.

  4. Puts server logic in the wrong directory. Convex functions must live in the convex/ directory. Claude frequently creates files in src/api/ or server/ 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

  1. Scheduling in mutations. Claude tries to use setTimeout inside mutations. Convex mutations cannot use timers — use ctx.scheduler.runAfter() to schedule delayed work.

  2. Importing between function types. Claude imports queries inside mutations for reuse. Convex does not allow calling queries from mutations directly. Use ctx.db calls in both, or extract shared logic into plain helper functions.

  3. File upload handling. Claude generates multipart form parsing code. Convex uses storage.generateUploadUrl() on the client side and storage.getUrl() for retrieval — there is no server-side file parsing.