#proper deletion
5 messages · Page 1 of 1 (latest)
Hi @turbid vault. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
Convex doesn’t have built‑in cascading deletes, but there are a few supported patterns to make this manageable and scalable.
1. Simple cascade in a single mutation (small fan‑out)
For relationships where each document only has a small number of dependents, you can delete related docs in the same mutation using queries + ctx.db.delete. This is transactional but bounded by mutation limits. [Generic writer]
Example pattern (user → messages):
export const deleteUser = mutation({
args: { userId: v.id("users") },
handler: async (ctx, { userId }) => {
// Delete related messages
const messages = await ctx.db
.query("messages")
.withIndex("owner", q => q.eq("owner", userId))
.collect();
for (const message of messages) {
await ctx.db.delete(message._id);
}
// Finally delete the user
await ctx.db.delete("users", userId);
},
});
This is essentially what the triggers article shows for cascade deletes. [Cascade deletes]
2. Triggers for automatic cascades (still bounded)
You can register a trigger on a “parent” table so that when a document is deleted, the trigger deletes related documents. [Cascade deletes]
triggers.register("users", async (ctx, change) => {
if (change.operation === "delete") {
for await (const message of ctx.db
.query("messages")
.withIndex("owner", q => q.eq("owner", change.id))) {
await ctx.db.delete(message._id);
}
}
});
This keeps your application code simple, but has the same size/time limits as any mutation; for very large fan‑outs you’ll need an async approach. [Cascade deletes]
3. Async / batched cleanup for large graphs
For large cascades (e.g. “tenant” or “profile” ID used across many tables), you generally:
- Do a soft delete or mark the root as deleted.
- Use an async process (actions, scheduled functions, or the Workflow component) to:
- Query a batch of related docs by index.
- Delete that batch.
- Reschedule / continue until done.
Convex staff explicitly recommend using the Workflow component for robust cascading deletes because it tracks progress and retries automatically; each step is still bounded, but the workflow as a whole can run arbitrarily long. [Too many reads]
“If I were implementing cascading deletes today I would take a look at the Workflow component… Because of the guarantees… With workflows, workflow state is tracked and retries are automatic.” [Too many reads]
4. Using Convex Ents (library) for cascades
If you’re open to using a library, Convex Ents has built‑in deletion behaviors:
- Default: cascading delete of related documents in a single transaction (good for small fan‑outs).
- Soft deletion.
- Scheduled deletion: soft delete first, then cascade over many scheduled mutations so no single mutation does too much work. [Ents deletes]
This gives you “no dangling references” without hand‑rolling all the logic.
So, while there’s no automatic DB‑level cascade, the recommended scalable approaches are:
- Small relationships: delete related docs in the same mutation or via triggers.
- Large relationships: soft delete + async cleanup, ideally via the Workflow component or Ents’ scheduled deletion.