Add a New Convex Table
Add a table to the Convex schema with indexes, validators, and the queries and mutations that read and write it.
Add a new table to the Convex backend (packages/backend/convex). Read the
Convex Backend overview and the project's Convex
guidelines first; the rules there override generic Convex advice.
1. Define the table in the schema
Add it to defineSchema in
packages/backend/convex/schema.ts, with the indexes you will query by:
reports: defineTable({
organizationId: v.string(),
title: v.string(),
createdBy: v.string(),
}).index("by_organization", ["organizationId"]),Always query by an index rather than filtering full scans. The
Data Model page describes the existing tables and
conventions (app-owned *_profiles vs. Better Auth tables).
2. Add queries and mutations
Create a module (e.g. convex/reports/queries.ts and mutations.ts) using the
new function syntax with explicit args and returns validators:
export const listForOrganization = query({
args: { organizationId: v.string() },
returns: v.array(/* ... */),
handler: async (ctx, args) => {
return await ctx.db
.query("reports")
.withIndex("by_organization", (q) =>
q.eq("organizationId", args.organizationId),
)
.collect();
},
});3. Authorize every function
Never trust the client for identity or membership. Resolve the actor and check a
permission with requireAppPermission before reading
or writing org-scoped data:
const actor = await requireAppPermission(ctx, { permission: "organization.read" });
// use actor.organizationId, not an id from args4. Audit writes that matter
For security-, billing-, or admin-relevant changes, write an audit row in the same transaction — see Write an Audit Event.
After editing the schema, the Convex dev server regenerates types in
convex/_generated. Do not edit generated files by hand.