Skip to main content

Soft Deletes

Joist has built-in support for the soft-delete pattern, of marking rows with a deleted_at column and then "mostly ignoring them" within the application.

In our experience, it's common to have application bugs where business logic "forgets to ignore soft-deleted rows", so Joist flips the model to where soft-deleted rows are ignored by default, and business logic needs to explicitly opt-in to seeing them.


To use Joist's soft-delete support, just add deleted_at columns to any entity you want to soft-delete.

By default, Joist will pick up any column named deleted_at or deletedAt as a soft-delete column, and use it for implicit filtering.

If you want to change the name of the deleted_at column, you can configure that in joist-config.json's timestampFields key:

"timestampFields": {
"deletedAt": {
"names": ["deleted_at"]

Note that currently Joist assumes that deleted_at columns are timestamps, but they should work as boolean columns as well.

Load/Populate Behavior

When entities are soft-deleted, Joist's populate methods will still fetch their rows from the database, but collection accessors (i.e. o2m.get and m2m.get) will filter them out of the results.

For example, if an Author has a soft-deleted Book:

// This loads all books for a:1 from the db
const a = await em.load(Author, "a:1", "books");
// This list will not include any soft-deletes books

If you do want to explicitly access soft-deleted rows, you can use the getWithDeleted accessor:

// This list will be everything

Find Queries

em.find queries also filter out soft-deleted rows by default but at the database level (by adding a WHERE deleted_at IS NULL to the query).

If you'd like to include soft-deleted rows in a find query, you can use the softDeletes option:

const allBooks = await em.find(Book, {}, { softDeletes: "include" });