Convex
You are an expert in Convex — the open-source, reactive backend platform where queries are TypeScript code. You have deep knowledge of schema design, function authoring (queries, mutations, actions), real-time data subscriptions, authentication, file storage, scheduling, and deployment workflows across React, Next.js, Angular, Vue, Svelte, React Native, and server-side environments.
When to Use
- Use when building a new project with Convex as the backend
- Use when adding Convex to an existing React, Next.js, Angular, Vue, Svelte, or React Native app
- Use when designing schemas for a Convex document-relational database
- Use when writing or debugging Convex functions (queries, mutations, actions)
- Use when implementing real-time/reactive data patterns
- Use when setting up authentication with Convex Auth or third-party providers (Clerk, Auth0, etc.)
- Use when working with Convex file storage, scheduled functions, or cron jobs
- Use when deploying or managing Convex projects
Core Concepts
Convex is a document-relational database with a fully managed backend. Key differentiators:
- Reactive by default: Queries automatically re-run and push updates to all connected clients when underlying data changes
- TypeScript-first: All backend logic — queries, mutations, actions, schemas — is written in TypeScript
- ACID transactions: Serializable isolation with optimistic concurrency control
- No infrastructure to manage: Serverless, scales automatically, zero config
- End-to-end type safety: Types flow from schema → backend functions → client hooks
Function Types
| Type | Purpose | Can Read DB | Can Write DB | Can Call External APIs | Cached/Reactive |
|---|---|---|---|---|---|
| Query | Read data | ✅ | ❌ | ❌ | ✅ |
| Mutation | Write data | ✅ | ✅ | ❌ | ❌ |
| Action | Side effects | via runQuery | via runMutation | ✅ | ❌ |
| HTTP Action | Webhooks/custom endpoints | via runQuery | via runMutation | ✅ | ❌ |
Project Setup
New Project (Next.js)
npx create-next-app@latest my-app
cd my-app && npm install convex
npx convex dev
Add to Existing Project
npm install convex
npx convex dev
The npx convex dev command:
- Prompts you to log in (GitHub)
- Creates a project and deployment
- Generates
convex/folder for backend functions - Syncs functions to your dev deployment in real-time
- Creates
.env.localwithCONVEX_DEPLOYMENTandNEXT_PUBLIC_CONVEX_URL
Folder Structure
my-app/
├── convex/
│ ├── _generated/ ← Auto-generated (DO NOT EDIT)
│ │ ├── api.d.ts
│ │ ├── dataModel.d.ts
│ │ └── server.d.ts
│ ├── schema.ts ← Database schema definition
│ ├── tasks.ts ← Query/mutation functions
│ └── http.ts ← HTTP actions (optional)
├── .env.local ← CONVEX_DEPLOYMENT, NEXT_PUBLIC_CONVEX_URL
└── convex.json ← Project config (optional)
Schema Design
Define your schema in convex/schema.ts using the validator library:
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
users: defineTable({
name: v.string(),
email: v.string(),
avatarUrl: v.optional(v.string()),
tokenIdentifier: v.string(),
})
.index("by_token", ["tokenIdentifier"])
.index("by_email", ["email"]),
messages: defineTable({
authorId: v.id("users"),
channelId: v.id("channels"),
body: v.string(),
attachmentId: v.optional(v.id("_storage")),
})
.index("by_channel", ["channelId"])
.searchIndex("search_body", { searchField: "body" }),
channels: defineTable({
name: v.string(),
description: v.optional(v.string()),
isPrivate: v.boolean(),
}),
});
Validator Types
| Validator | TypeScript Type | Notes |
|---|---|---|
v.string() | string | |
v.number() | number | IEEE 754 float |
v.bigint() | bigint | |
v.boolean() | boolean | |
v.null() | null | |
v.id("tableName") | Id<"tableName"> | Document reference |
v.array(v.string()) | string[] | |
v.object({...}) | {...} | Nested objects |
v.optional(v.string()) | string | undefined | |
v.union(v.string(), v.number()) | string | number | |
v.literal("active") | "active" | Literal types |
v.bytes() | ArrayBuffer | Binary data |
v.float64() | number | Explicit 64-bit float (used in vector indexes) |
v.any() | any | Escape hatch |
Indexes
// Single-field index
defineTable({ email: v.string() }).index("by_email", ["email"]);
// Compound index (order matters for range queries)
defineTable({
orgId: v.string(),
createdAt: v.number(),
}).index("by_org_and_date", ["orgId", "createdAt"]);
// Full-text search index
defineTable({ body: v.string(), channelId: v.id("channels") }).searchIndex(
"search_body",
{
searchField: "body",
filterFields: ["channelId"],
},
);
// Vector search index (for AI/embeddings)
defineTable({ embedding: v.array(v.float64()), text: v.string() }).vectorIndex(
"by_embedding",
{
vectorField: "embedding",
dimensions: 1536,
},
);
Writing Functions
Queries (Read Data)
Queries are reactive — clients automatically get updates when data changes.
import { query } from "./_generated/server";
import { v } from "convex/values";
// Simple query — list all tasks
export const list = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query("tasks").collect();
},
});
// Query with arguments and filtering
export const getByChannel = query({
args: { channelId: v.id("channels") },
handler: async (ctx, args) => {
return await ctx.db
.query("messages")
.withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
.order("desc")
.take(50);
},
});
// Query with auth check
export const getMyProfile = query({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) return null;
return await ctx.db
.query("users")
.withIndex("by_token", (q) =>
q.eq("tokenIdentifier", identity.tokenIdentifier),
)
.unique();
},
});
### Paginated Queries
Use cursor-based pagination for lists or infinite scroll UIs.
```typescript
import { query } from "./_generated/server";
import { paginationOptsValidator } from "convex/server";
export const listPaginated = query({
args: {