PayloadCMS - How to set up ON DELETE CASCADE in collections?
Author
joprocorp
Date Published

This package is available in NPM under @lizardglobal/payload-collection-references .
This plugin is still experimental. APIs, collection schemas, and behavior may change without a stable compatibility guarantee. Use in production with caution and pin versions deliberately. We are open to feedback and contributions! Don't hesitate to reach out.
PayloadCMS is a powerful content management system that abstracts away a lot of complexities that are superfluous to feature implementation. It adds tools such as hooks to handle action side-effects and many more things, but something I found, and others found, it lacks is deletion cascade management.
In Postgres, when an entry in a table gets deleted, you get the possibility to specify what happens using the ON DELETE instruction. You have three options:
- CASCADE; When the primary record is deleted, all related records (those referencing it via a foreign key) are also removed.
- RESTRICT (or NO ACTION); You can't delete a primary record if there are related records in other tables. This is the default behaviour.
- SET NULL; Deleting the primary record will change the foreign key in the dependent records to NULL, rather than deleting those records.
- SET DEFAULT. Deleting the primary record changes the foreign key in related records to a default value that you've specified.
How does PayloadCMS handles this?
It doesn't. There is no official stance on the matter. The maintainers have not yet said whether this is a feature that will be supported in the future or not, but so far there has been no communication of it being implemented. So, it is safe for now to assume it won't. One user said:
I suppose the issue is that CASCADE delete will not trigger hooks on related collections. That's why noone from Payload CMS replied :-D. It can't be handled on DB layer unless we are sure that the related collection doesn't have on delete hook. - tolszak
I agree. Leaving the database layer to handle cascade deletes will bypass Payload's abstraction layer and will not trigger other hooks and thus will break implicit and explicit side-effects application rules. In addition to that, since Payload also abstracts the database layer and supports both relational and non-relational databases, adding a built-in ON DELETE handler would only work for databases that support it, and forcing the maintainers to create inconsistent behaviours between databases, or having to implement a workaround.
So, in a broad general sense, I agree that Payload doesn't need to, or shouldn't, implement relational-database-specific on-delete behaviour as a first-citizen built-in functionality.
What do we do then?
So the solution seems pretty obvious then: we need to implement cascade delete ourselves with afterDelete hooks and such. Well... yes. But who said we had to do it manually. So I present to you, payload-collection-references, a package that scans your collections, automatically detects relationship references, and adds cascade deletion hooks! And in bonus, you get a nice utility library to retrieve relationships and references.
How does it work?
Under the hood, @lizardglobal/payload-collection-references acts as an automated middleware for Payload’s schema and lifecycle hooks. Here is a breakdown of what happens when your application initializes:
- Schema Introspection: During Payload startup, the plugin scans all registered collections and builds a dynamic dependency graph by inspecting fields of type
relationshipandupload. - Hook Injection: Based on the relationships it discovers (or your explicit configuration), it automatically attaches
beforeDeletehooks to target collections. - Cascading Cleanup: When a document is deleted through Payload’s Local, REST, or GraphQL API, the injected
beforeDeletehook triggers nested operations—such as deleting referencing documents (CASCADE) or unsetting field values (SET_NULL)—using Payload’s Local API (payload.deleteorpayload.update). - Hook Lifecycle Preservation: Because actions are executed via the Local API inside Payload's runtime, all dependent
beforeDelete,afterDelete, and access control hooks defined on child collections run as intended.
Installation
For a better and more accurate usage guide, please visit @lizardglobal/payload-richtext-lexical-react-native or the github repository.
You can install @lizardglobal/payload-collection-references via your package manager of choice:
1# npm2npm install @lizardglobal/payload-collection-references34# pnpm5pnpm add @lizardglobal/payload-collection-references67# yarn8yarn add @lizardglobal/payload-collection-references
To register the plugin, add it to your main payload.config.ts file inside the plugins array. Either leaving it to figure out the relationship graph on its own:
1import { collectionReferencesPlugin } from 'payload-collection-references'23export default buildConfig({4 collections: [5 {6 slug: 'posts',7 fields: [8 {9 name: 'author',10 type: 'relationship',11 relationTo: 'users',12 },13 {14 name: 'heroImage',15 type: 'upload',16 relationTo: 'media',17 },18 ],19 },20 ],21 plugins: [22 collectionReferencesPlugin(),23 ],24})
Or by providing explicit field declarations:
1collectionReferencesPlugin({2 declarations: {3 // "posts" owns both of these relationship fields:4 posts: [5 {6 fieldPath: 'heroImage', // field in "posts" that references "media"7 onDelete: 'unlink', // when the media doc is deleted, set heroImage to null8 referencedCollection: 'media',9 },10 {11 fieldPath: 'author', // field in "posts" that references "users"12 onDelete: 'delete', // when the user is deleted, cascade-delete their posts13 referencedCollection: 'users',14 },15 ],16 },17})
The property onDelete can take the value delete or unlink. When providing delete, it will cascade-deletes all documents in the referencing collection that point to the deleted document, which triggers their own beforeDelete hooks recursively.
When providing unlink, it will set the relationship field to null (or a custom unlinkValue) on all matching documents, while the referencing documents are preserved.
That's it! Now, if you delete a User, all of their posts will be deleted as well. And if you delete the image's media, then the post's image will be nulled.

PayloadCMS - How to render Lexical Editor content in React Native?
React Native implementation of the PayloadCMS Rich Text Renderer for serialized Lexical Editor content

PayloadCMS - How to "dry-run" a CREATE operation?
PayloadCMS plugin to dry-run your create operations without persisting data — full validation, hooks, and business logic, automatically rolled back.