|
| 1 | +--- |
| 2 | +layout: default |
| 3 | +title: "Soft Delete Setup" |
| 4 | +--- |
| 5 | + |
| 6 | +The soft delete feature is an [Enterprise Edition add-on](https://react-admin-ee.marmelab.com/documentation/ra-soft-delete) that allows you to "delete" records without actually removing them from your database. |
| 7 | + |
| 8 | +Use it to: |
| 9 | + |
| 10 | +- Archive records safely instead of permanent deletion |
| 11 | +- Browse and filter all deleted records in a dedicated interface |
| 12 | +- Restore archived items individually or in bulk |
| 13 | +- Track who deleted what and when |
| 14 | + |
| 15 | +It provides drop-in replacements for DeleteButton and BulkDeleteButton. |
| 16 | + |
| 17 | +## Installation |
| 18 | + |
| 19 | +```bash |
| 20 | +npm install --save @react-admin/ra-soft-delete |
| 21 | +# or |
| 22 | +yarn add @react-admin/ra-soft-delete |
| 23 | +``` |
| 24 | + |
| 25 | +You will need an active Enterprise Edition license to use this package. Please refer to the [Enterprise Edition documentation](https://react-admin-ee.marmelab.com) for more details. |
| 26 | + |
| 27 | +## Data Provider |
| 28 | + |
| 29 | +### Methods |
| 30 | + |
| 31 | +`ra-soft-delete` relies on the `dataProvider` to soft-delete, restore or view deleted records. |
| 32 | +In order to use the `ra-soft-delete`, you must add a few new methods to your data provider: |
| 33 | + |
| 34 | +- `softDelete` performs the soft deletion of the provided record. |
| 35 | +- `softDeleteMany` performs the soft deletion of the provided records. |
| 36 | +- `getOneDeleted` gets one deleted record by its ID. |
| 37 | +- `getListDeleted` gets a list of deleted records with filters and sort. |
| 38 | +- `restoreOne` restores a deleted record. |
| 39 | +- `restoreMany` restores deleted records. |
| 40 | +- `hardDelete` permanently deletes a record. |
| 41 | +- `hardDeleteMany` permanently deletes many records. |
| 42 | +- (OPTIONAL) [`createMany`](#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. |
| 43 | + |
| 44 | +### Signature |
| 45 | + |
| 46 | +Here is the full `SoftDeleteDataProvider` interface: |
| 47 | + |
| 48 | +```tsx |
| 49 | +const dataProviderWithSoftDelete: SoftDeleteDataProvider = { |
| 50 | + ...dataProvider, |
| 51 | + |
| 52 | + softDelete: (resource, params: SoftDeleteParams): SoftDeleteResult => { |
| 53 | + const { id, authorId } = params; |
| 54 | + // ... |
| 55 | + return { data: deletedRecord }; |
| 56 | + }, |
| 57 | + softDeleteMany: (resource, params: SoftDeleteManyParams): SoftDeleteManyResult => { |
| 58 | + const { ids, authorId } = params; |
| 59 | + // ... |
| 60 | + return { data: deletedRecords }; |
| 61 | + }, |
| 62 | + |
| 63 | + getOneDeleted: (params: GetOneDeletedParams): GetOneDeletedResult => { |
| 64 | + const { id } = params; |
| 65 | + // ... |
| 66 | + return { data: deletedRecord }; |
| 67 | + }, |
| 68 | + getListDeleted: (params: GetListDeletedParams): GetListDeletedResult => { |
| 69 | + const { filter, sort, pagination } = params; |
| 70 | + // ... |
| 71 | + return { data: deletedRecords, total: deletedRecords.length }; |
| 72 | + }, |
| 73 | + |
| 74 | + restoreOne: (params: RestoreOneParams): RestoreOneResult => { |
| 75 | + const { id } = params; |
| 76 | + // ... |
| 77 | + return { data: deletedRecord }; |
| 78 | + }, |
| 79 | + restoreMany: (params: RestoreManyParams): RestoreManyResult => { |
| 80 | + const { ids } = params; |
| 81 | + // ... |
| 82 | + return { data: deletedRecords }; |
| 83 | + }, |
| 84 | + |
| 85 | + hardDelete: (params: HardDeleteParams): HardDeleteResult => { |
| 86 | + const { id } = params; |
| 87 | + // ... |
| 88 | + return { data: deletedRecordId }; |
| 89 | + }, |
| 90 | + hardDeleteMany: (params: HardDeleteManyParams): HardDeleteManyResult => { |
| 91 | + const { ids } = params; |
| 92 | + // ... |
| 93 | + return { data: deletedRecordsIds }; |
| 94 | + }, |
| 95 | +}; |
| 96 | +``` |
| 97 | + |
| 98 | +**Tip**: `ra-soft-delete` automatically populates the `authorId` parameter using `authProvider.getIdentity()` if it is implemented. It will use the `id` field of the returned identity object. Otherwise this field will be left blank. |
| 99 | + |
| 100 | +**Tip**: Deleted records are immutable, so you don't need to implement an `updateDeleted` method. |
| 101 | + |
| 102 | +Once your provider has all soft-delete methods, pass it to the `<Admin>` component and you're ready to start using `ra-soft-delete`. |
| 103 | + |
| 104 | +```tsx |
| 105 | +// in src/App.tsx |
| 106 | +import { Admin } from 'react-admin'; |
| 107 | +import { dataProvider } from './dataProvider'; |
| 108 | + |
| 109 | +const App = () => <Admin dataProvider={dataProvider}>{/* ... */}</Admin>; |
| 110 | +``` |
| 111 | + |
| 112 | +### Deleted Record Structure |
| 113 | + |
| 114 | +A _deleted record_ is an object with the following properties: |
| 115 | + |
| 116 | +- `id`: The identifier of the deleted record. |
| 117 | +- `resource`: The resource name of the deleted record. |
| 118 | +- `deleted_at`: The date and time when the record was deleted, in ISO 8601 format. |
| 119 | +- `deleted_by`: (optional) The identifier of the user who deleted the record. |
| 120 | +- `data`: The original record data before deletion. |
| 121 | + |
| 122 | +Here is an example of a deleted record: |
| 123 | + |
| 124 | +```js |
| 125 | +{ |
| 126 | + id: 123, |
| 127 | + resource: "products", |
| 128 | + deleted_at: "2025-06-06T15:32:22Z", |
| 129 | + deleted_by: "johndoe", |
| 130 | + data: { |
| 131 | + id: 456, |
| 132 | + title: "Lorem ipsum", |
| 133 | + teaser: "Lorem ipsum dolor sit amet", |
| 134 | + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', |
| 135 | + }, |
| 136 | +} |
| 137 | +``` |
| 138 | + |
| 139 | +### Builders |
| 140 | + |
| 141 | +`ra-soft-delete` 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: |
| 142 | + |
| 143 | +- [`addSoftDeleteBasedOnResource`](./addSoftDeleteBasedOnResource.md) stores the deleted records for all resources in a single resource. This resource is named `deleted_records` by default. |
| 144 | + |
| 145 | + With this builder, all deleted records disappear from their original resource when soft-deleted, and are recreated in the `deleted_records` resource. |
| 146 | + |
| 147 | +```tsx |
| 148 | +// in src/dataProvider.ts |
| 149 | +import { addSoftDeleteBasedOnResource } from '@react-admin/ra-soft-delete'; |
| 150 | +import baseDataProvider from './baseDataProvider'; |
| 151 | + |
| 152 | +export const dataProvider = addSoftDeleteBasedOnResource( |
| 153 | + baseDataProvider, |
| 154 | + { deletedRecordsResourceName: 'deleted_records' } |
| 155 | +); |
| 156 | +``` |
| 157 | + |
| 158 | +- [`addSoftDeleteInPlace`](./addSoftDeleteInPlace.md) keeps the deleted records in the same resource, but marks them as deleted. |
| 159 | + |
| 160 | + 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. |
| 161 | + |
| 162 | + 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. |
| 163 | + |
| 164 | +```tsx |
| 165 | +// in src/dataProvider.ts |
| 166 | +import { addSoftDeleteInPlace } from '@react-admin/ra-soft-delete'; |
| 167 | +import baseDataProvider from './baseDataProvider'; |
| 168 | + |
| 169 | +export const dataProvider = addSoftDeleteInPlace( |
| 170 | + baseDataProvider, |
| 171 | + { |
| 172 | + posts: {}, |
| 173 | + comments: { |
| 174 | + deletedAtFieldName: 'deletion_date', |
| 175 | + }, |
| 176 | + accounts: { |
| 177 | + deletedAtFieldName: 'disabled_at', |
| 178 | + deletedByFieldName: 'disabled_by', |
| 179 | + } |
| 180 | + } |
| 181 | +); |
| 182 | +``` |
| 183 | + |
| 184 | +**Note:** When using `addSoftDeleteInPlace`, avoid calling `getListDeleted` without a `resource` filter, as it uses a naive implementation combining multiple `getList` calls, which can lead to bad performance. It is recommended to use one list per resource in this case (see [`<DeletedRecordsListBase resource>` property](./DeletedRecordsListBase.md#resource)). |
| 185 | + |
| 186 | +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`. |
| 187 | + |
| 188 | +### Query and Mutation Hooks |
| 189 | + |
| 190 | +Each data provider verb has its own hook so you can use them in custom components: |
| 191 | + |
| 192 | +- `softDelete`: [`useSoftDelete`](./useSoftDelete.md) |
| 193 | +- `softDeleteMany`: [`useSoftDeleteMany`](./useSoftDeleteMany.md) |
| 194 | +- `getListDeleted`: [`useGetListDeleted`](./useGetListDeleted.md) |
| 195 | +- `getOneDeleted`: [`useGetOneDeleted`](./useGetOneDeleted.md) |
| 196 | +- `restoreOne`: [`useRestoreOne`](./useRestoreOne.md) |
| 197 | +- `restoreMany`: [`useRestoreMany`](./useRestoreMany.md) |
| 198 | +- `hardDelete`: [`useHardDelete`](./useHardDelete.md) |
| 199 | +- `hardDeleteMany`: [`useHardDeleteMany`](./useHardDeleteMany.md) |
| 200 | + |
| 201 | + |
| 202 | +## `createMany` |
| 203 | + |
| 204 | +`ra-soft-delete` provides a default implementation of the `createMany` method that simply calls `create` multiple times. However, some data providers may be able to create multiple records at once, which can greatly improve performances. |
| 205 | + |
| 206 | +```tsx |
| 207 | +const dataProviderWithCreateMany = { |
| 208 | + ...dataProvider, |
| 209 | + createMany: (resource, params: CreateManyParams): CreateManyResult => { |
| 210 | + const {data} = params; // data is an array of records. |
| 211 | + // ... |
| 212 | + return {data: createdRecords}; |
| 213 | + }, |
| 214 | +}; |
| 215 | +``` |
0 commit comments