Overview
For aggregates to be accurate, every table modification must update the associated aggregate. If they get out of sync, your computed aggregates will be incorrect. There are three main approaches to keeping data synchronized:- Manual updates in every mutation
- Encapsulated write functions (recommended)
- Automatic triggers
Manual Updates
The simplest approach is to manually update aggregates in each mutation:Encapsulated Write Functions (Recommended)
Place all table writes in separate TypeScript functions, then always call these functions from mutations:- Encapsulates table write logic
- Makes updates explicit and testable
- Prevents forgetting to update aggregates
- Works well with TypeScript’s type system
Automatic Triggers
Use the Triggers helper from convex-helpers to automatically update aggregates:Setup
First, install convex-helpers:Register Triggers
Use Wrapped Mutations
Now use your custommutation function instead of the default:
Trigger Behavior
The trigger automatically calls:aggregate.insert(ctx, doc)on table insertsaggregate.replace(ctx, oldDoc, newDoc)on table updatesaggregate.delete(ctx, oldDoc)on table deletes
Multiple Aggregates on One Table
You can register multiple triggers for the same table:Idempotent Triggers for Backfills
During migrations or backfills, use idempotent triggers that don’t throw errors if items already exist:insertIfDoesNotExistinstead ofinsertreplaceOrInsertinstead ofreplacedeleteIfExistsinstead ofdelete
Choosing an Approach
Use Manual Updates when:
- You have only a few mutations
- You want maximum control and visibility
- Your team prefers explicit over implicit behavior
Use Encapsulated Functions when:
- You want explicit control with better organization
- You need testable, reusable write operations
- You want type safety and refactoring support
Use Triggers when:
- You have many mutations modifying the same table
- You want the cleanest mutation code
- You’re comfortable with automatic behavior
- You’re already using convex-helpers
Atomicity Guarantees
All approaches benefit from Convex’s atomic mutations:- No race conditions between table and aggregate writes
- No query can observe a document in the table but not in the aggregate
- Mutations run transactionally
Debugging Out-of-Sync Aggregates
If aggregates become incorrect, you may need to:- Identify which mutations aren’t updating aggregates
- Add missing aggregate updates
- Run a backfill to fix existing data (see Migrations and Backfills)
Next Steps
- Learn about Migrations and Backfills to fix out-of-sync data
- Understand Table Aggregates configuration
- Explore Batch Operations for performance