Skip to content

Soft Delete

Shadcn Admin Kit provides hooks and components to let users “delete” records without actually removing them from your database.

Use it to:

  • Archive records safely instead of permanent deletion
  • Require a second confirmation step before permanent deletion (“four-eyes” principle)
  • Browse and filter all deleted records in a dedicated interface
  • Restore archived items individually or in bulk
  • Track who deleted what and when

The soft delete features require a valid Enterprise Edition subscription. Once subscribed, follow the instructions to get access to the private npm repository.

You can then install the npm package providing the realtime features using your favorite package manager:

Terminal window
npm install --save @react-admin/ra-core-ee
# or
yarn add @react-admin/ra-core-ee

The Soft Delete packages let you build a complete soft delete experience in your admin, including:

It leverages the following hooks to interact with the data provider:

In order to use the Soft Delete features, your data provider must implement a few new methods.

The Soft Delete features of ra-core-ee rely on the dataProvider to soft-delete, restore or view deleted records. In order to use those features, you must add a few new methods to your data provider:

  • softDelete performs the soft deletion of the provided record.
  • softDeleteMany performs the soft deletion of the provided records.
  • getOneDeleted gets one deleted record by its ID.
  • getListDeleted gets a list of deleted records with filters and sort.
  • restoreOne restores a deleted record.
  • restoreMany restores deleted records.
  • hardDelete permanently deletes a record.
  • hardDeleteMany permanently deletes many records.
  • (OPTIONAL) createMany creates multiple records at once. This method is used internally by some data provider implementations to delete or restore multiple records at once. As it is optional, a default implementation is provided that simply calls create multiple times.

Here is the full SoftDeleteDataProvider interface:

const dataProviderWithSoftDelete: SoftDeleteDataProvider = {
...dataProvider,
softDelete: (resource, params: SoftDeleteParams): SoftDeleteResult => {
const { id, authorId } = params;
// ...
return { data: deletedRecord };
},
softDeleteMany: (
resource,
params: SoftDeleteManyParams,
): SoftDeleteManyResult => {
const { ids, authorId } = params;
// ...
return { data: deletedRecords };
},
getOneDeleted: (params: GetOneDeletedParams): GetOneDeletedResult => {
const { id } = params;
// ...
return { data: deletedRecord };
},
getListDeleted: (params: GetListDeletedParams): GetListDeletedResult => {
const { filter, sort, pagination } = params;
// ...
return { data: deletedRecords, total: deletedRecords.length };
},
restoreOne: (params: RestoreOneParams): RestoreOneResult => {
const { id } = params;
// ...
return { data: deletedRecord };
},
restoreMany: (params: RestoreManyParams): RestoreManyResult => {
const { ids } = params;
// ...
return { data: deletedRecords };
},
hardDelete: (params: HardDeleteParams): HardDeleteResult => {
const { id } = params;
// ...
return { data: deletedRecordId };
},
hardDeleteMany: (params: HardDeleteManyParams): HardDeleteManyResult => {
const { ids } = params;
// ...
return { data: deletedRecordsIds };
},
};

Once your provider has all soft-delete methods, pass it to the <Admin> component and you’re ready to start using the Soft Delete feature.

// in src/App.tsx
import { Admin } from '@/components/admin/admin';
import { dataProvider } from './dataProvider';
const App = () => <Admin dataProvider={dataProvider}>{/* ... */}</Admin>;

A deleted record is an object with the following properties:

  • id: The identifier of the deleted record.
  • resource: The resource name of the deleted record.
  • deleted_at: The date and time when the record was deleted, in ISO 8601 format.
  • deleted_by: (optional) The identifier of the user who deleted the record.
  • data: The original record data before deletion.

Here is an example of a deleted record:

{
id: 123,
resource: "products",
deleted_at: "2025-06-06T15:32:22Z",
deleted_by: "johndoe",
data: {
id: 456,
title: "Lorem ipsum",
teaser: "Lorem ipsum dolor sit amet",
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
},
}

ra-core-ee comes with two built-in implementations that will add soft delete capabilities to your data provider without any specific backend requirements. You can choose the one that best fits your needs:

  • addSoftDeleteBasedOnResource stores the deleted records for all resources in a single resource. This resource is named deleted_records by default.

    With this builder, all deleted records disappear from their original resource when soft-deleted, and are recreated in the deleted_records resource.

// in src/dataProvider.ts
import { addSoftDeleteBasedOnResource } from '@react-admin/ra-core-ee';
import baseDataProvider from './baseDataProvider';
export const dataProvider = addSoftDeleteBasedOnResource(baseDataProvider, {
deletedRecordsResourceName: 'deleted_records',
});
  • addSoftDeleteInPlace keeps the deleted records in the same resource, but marks them as deleted.

    With this builder, all deleted records remain in their original resource when soft-deleted, but are marked with the deleted_at and deleted_by fields. The query methods (getList, getOne, etc.) automatically filter out deleted records.

    You’ll need to pass a configuration object with all soft deletable resources as key so that getListDeleted knows where to look for deleted records.

// in src/dataProvider.ts
import { addSoftDeleteInPlace } from '@react-admin/ra-core-ee';
import baseDataProvider from './baseDataProvider';
export const dataProvider = addSoftDeleteInPlace(baseDataProvider, {
posts: {},
comments: {
deletedAtFieldName: 'deletion_date',
},
accounts: {
deletedAtFieldName: 'disabled_at',
deletedByFieldName: 'disabled_by',
},
});

You can also write your own implementation. Feel free to look at these builders source code for inspiration. You can find it under your node_modules folder, e.g. at node_modules/@react-admin/ra-core-ee/src/soft-delete/dataProvider/addSoftDeleteBasedOnResource.ts.

The <BulkSoftDeleteButton> has to create or update several records at once. If your data provider supports the createMany method to create multiple records at once, it will use it instead of calling create multiple times for performance reasons.

const dataProviderWithCreateMany = {
...dataProvider,
createMany: (resource, params: CreateManyParams): CreateManyResult => {
const { data } = params; // data is an array of records.
// ...
return { data: createdRecords };
},
};