Skip to main content
Convex provides a fully transactional database with reactive queries. All reads and writes within a single mutation execute atomically.

Database reader

The GenericDatabaseReader interface provides read-only access to the database in queries. Access it via ctx.db in query and mutation functions.

Get a document by ID

Fetch a single document from the database by its ID:
You can also specify the table name explicitly:
GenericId<TableName>
The ID of the document to fetch from the database.
string
The name of the table to fetch the document from (optional first parameter).
Returns: The document at the given ID, or null if it no longer exists.

Query documents

Begin a query for a given table. Queries don’t execute immediately - they’re lazily evaluated when you consume the results.
string
The name of the table to query.
Returns: A QueryInitializer object to start building a query.

System tables

Access system tables like _storage (file metadata) and _scheduled_functions (scheduled function state):

Normalize ID

Returns the string ID format for an ID in a given table, or null if the ID is from a different table or is not valid:
string
The name of the table.
string
The ID string to normalize.
Returns: The normalized GenericId<TableName> or null.

Database writer

The GenericDatabaseWriter interface extends GenericDatabaseReader with write operations. Available as ctx.db in mutations. All reads and writes within a single mutation are executed atomically - you never have to worry about partial writes leaving your data in an inconsistent state.

Insert

Insert a new document into a table:
string
The name of the table to insert into.
object
The document to insert. System fields (_id, _creationTime) are added automatically and should not be included.
Returns: The GenericId of the newly created document.

Patch

Patch an existing document, shallow merging it with the given partial document:
New fields are added. Existing fields are overwritten. Fields set to undefined are removed. Fields not specified in the patch are left unchanged.
GenericId<TableName>
The ID of the document to patch.
Partial<Document>
The partial document to merge into the existing document.
Tip: Use patch for partial updates. Use replace when you want to overwrite the entire document. Throws: An error if the document does not exist.

Replace

Replace the entire value of an existing document, overwriting its old value completely:
Unlike patch, which does a shallow merge, replace overwrites the entire document. Any fields not included in the new value will be removed (except system fields _id and _creationTime).
GenericId<TableName>
The ID of the document to replace.
Document
The new document. System fields can be omitted.
Throws: An error if the document does not exist.

Delete

Delete an existing document:
To delete multiple documents, collect them first, then delete each one:
GenericId<TableName>
The ID of the document to remove.
Note: Convex queries do not support .delete() directly on query results. Collect documents first, then delete each one individually.

Table scoped API

Scope the database to a specific table for cleaner syntax:

Best practices

  • Use .withIndex() instead of .filter() for efficient queries. Define indexes in your schema for fields you query frequently.
  • Avoid .collect() on unbounded queries - it loads all matching documents into memory. Prefer .first(), .unique(), .take(n), or pagination.
  • All mutations are atomic - reads and writes within a single mutation see a consistent snapshot and commit together.
  • System fields like _id and _creationTime are automatically managed - don’t include them when inserting documents.