Insert
Insert rows with Drizzle-style builders
In this guide, we'll learn how to insert rows into your Convex tables using the ORM's Drizzle-style insert() builder. You'll see single inserts, multi-row inserts, returning clauses, and upsert patterns.
Basic Insert
Let's start with a simple mutation that inserts a single user:
import { z } from 'zod';
import { publicMutation } from '../lib/crpc';
import { users } from '../schema';
export const createUser = publicMutation
.input(z.object({ email: z.string().email(), name: z.string() }))
.mutation(async ({ ctx, input }) => {
await ctx.orm.insert(users).values({
email: input.email,
name: input.name,
});
});If you need the insert type for a table, you can infer it from the table definition:
type NewUser = typeof users.$inferInsert;Returning
By default, insert() returns void. Use .returning() to get the inserted rows back.
You can return all fields or pick specific columns:
const [user] = await ctx.orm
.insert(users)
.values({ email: 'ada@example.com', name: 'Ada' })
.returning();
const [partial] = await ctx.orm
.insert(users)
.values({ email: 'grace@example.com', name: 'Grace' })
.returning({ id: users.id, email: users.email });Note: .returning() always returns an array, even for single-row inserts.
Insert Multiple Rows
You can pass an array to .values() to insert several rows at once:
await ctx.orm.insert(users).values([
{ email: 'a@example.com', name: 'A' },
{ email: 'b@example.com', name: 'B' },
]);Upserts and Conflicts
The ORM mirrors Drizzle's onConflictDoNothing() and onConflictDoUpdate() surface, enforced at runtime against your table's unique constraints and indexes.
On Conflict Do Nothing
Use onConflictDoNothing to silently skip rows that would violate a unique constraint:
await ctx.orm
.insert(users)
.values({ email: 'ada@example.com', name: 'Ada' })
.onConflictDoNothing({ target: users.email });You can also omit target to skip inserts on any unique conflict:
await ctx.orm
.insert(users)
.values({ email: 'ada@example.com', name: 'Ada' })
.onConflictDoNothing();On Conflict Do Update
Use onConflictDoUpdate to update the existing row when a conflict occurs:
await ctx.orm
.insert(users)
.values({ email: 'ada@example.com', name: 'Ada' })
.onConflictDoUpdate({
target: users.email,
set: { name: 'Ada Lovelace' },
});Important: onConflictDoUpdate() requires a target column and a backing unique constraint or index (uniqueIndex(), .unique(), unique()).
Constraint and RLS Enforcement
The ORM enforces unique constraints, foreign keys, and RLS policies at runtime for all insert and upsert operations.
Note: Direct ctx.db.insert(...) bypasses these checks. Use ctx.orm to get full constraint enforcement.
That's it. You now have everything you need to insert data - from simple single-row inserts to conflict-handling upserts.