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

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

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

Hard delete removes the row irreversibly and requires the permanentDelete permission. When force-delete is not allowed for a table, 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.

tables:
  - id: 1
    name: orders
    permissions:
      delete: ['admin', 'member']
      permanentDelete: ['admin'] # Permanent delete requires admin role
    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

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.

{
  "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.

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)
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 permanentDelete (typically admin only)
Restore delete / restore grant for the role