Skip to main content
View as Markdown

Soft Delete & Restore

Deleting a record is non-destructive by default: the row is marked deleted (deletedAt/deletedBy stamped) and disappears from normal queries, but remains recoverable. This protects against accidental deletion, supports a trash/undo workflow, and keeps an audit trail. Permanent (hard) delete is a separate, permission-gated operation for cases that demand irreversible erasure.

Method & Path Description
DELETE /api/tables/:tableId/records/:recordId Soft delete a record
DELETE /api/tables/:tableId/records/:recordId?permanent=true Permanently delete a record
GET /api/tables/:tableId/trash Browse soft-deleted records
POST /api/tables/:tableId/records/:recordId/restore Restore a soft-deleted record
POST /api/tables/:tableId/records/batch/restore Restore many records

Soft delete

code
DELETE /api/tables/orders/records/123

This sets deletedAt to the current time and deletedBy to the acting user, then excludes the row from default list and read responses. The operation is logged to the record's change history.

Permanent (hard) delete

code
DELETE /api/tables/orders/records/123?permanent=true

Hard delete removes the row irreversibly and is reserved for the admin role on every table — it is not configurable through table permissions. When the acting user is not an admin, the endpoint returns 404 (anti-enumeration) rather than 403.

When a record is deleted, related records are handled per the relationship field's onDelete policy. Configure it on the relational field.

app.yaml
tables:
  - id: 1
    name: orders
    permissions:
      delete: ['admin', 'member'] # Also governs restore
    fields:
      - id: 1
        name: customer_id
        type: relationship
        relatedTable: customers
        onDelete: cascade # cascade | set-null | restrict
onDelete Effect when the parent is deleted
cascade Delete the dependent (child) records too
set-null Clear the foreign-key reference on dependents
restrict Block the delete while dependents exist

Trash view

GET /api/tables/:tableId/trash lists soft-deleted records so a UI can present a recoverable bin. The same result is available on the list endpoint via includeDeleted=only.

Restore

code
POST /api/tables/orders/records/123/restore

Restore clears deletedAt (returning the row to normal queries), stamps a restored_at, captures the restoring user, and logs the operation to history.

app.json
{
  "id": 123,
  "deleted_at": null,
  "restored_at": "2025-01-15T11:00:00Z"
}
Status Meaning
200 OK Record restored
400 Bad Request Record is not currently deleted
401 Unauthorized No active session
404 Not Found Record absent or not visible

Batch restore

Recover many records in one transaction. Records that are not currently deleted are skipped; a missing id rolls the whole batch back (404). See Batch Operations.

app.json
POST /api/tables/orders/records/batch/restore
{
  "ids": ["123", "124", "125"]
}

Querying deleted records

By default, soft-deleted rows are excluded from list and read responses. Override with the includeDeleted query parameter:

Value Behavior
(omitted) Active records only (default)
true Active and deleted records
only Soft-deleted records only (equivalent to the trash view)
code
GET /api/tables/orders/records?includeDeleted=true
GET /api/tables/orders/records?includeDeleted=only

Permissions summary

Operation Permission
Soft delete delete (per table permissions + RBAC)
Permanent delete admin role only — not configurable through table permissions
Restore delete — the same grant as soft delete, on both restore endpoints

Last updated August 11, 2026

This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.

Built with Sovrium