Skip to main content
Indexes turn O(n) table scans into O(log n) lookups. Add them whenever you filter or sort by a field on a non-tiny table.

Defining an index

convex/schema.ts
Naming: by_<field> for single-column, by_<a>_and_<b> for compound.

Using an index

The q chain inside withIndex matches the index’s column order.

When to add an index

✅ When you filter by it

.filter(t => t.field === value) is O(n). .withIndex(...) is O(log n).

✅ When you sort by it

Sorted-order queries on indexed fields are free; on non-indexed, you read everything and sort in memory.

❌ For tiny tables

< 100 rows: no benefit. Don’t bother.

❌ Pre-emptively

Each index slightly slows writes. Add when a query is slow, not before.

Compound indexes follow query order

["teamId", "status"] accelerates queries that filter by teamId (or teamId + status together). It does not accelerate filtering by status alone. For “filter by team OR by status independently,” create both indexes:

Built-in indexes

Every table has an implicit index on _id (the primary key) and _creationTime. So .order("desc").take(10) for “most recent 10” is always fast.

Inspecting query performance

vly’s editor flags slow queries in the console. For deeper inspection:
Shows top slow queries, top missed-index queries, and recommendations.

Tips

When you add an index, vly auto-rewrites existing queries to use it (if applicable). No manual update needed.
For “find by exact field value,” always index. The where field = X pattern is the bread and butter — index it.

Schema

Index syntax.

Queries

Using indexes.

Performance issues

Debugging slow queries.
Last modified on April 18, 2026