# Soft delete and data retention > Audience: administrators. This page is hidden from members and guests in the sidebar, the search > index, and the documentation export. It describes the platform-wide soft-delete model, the data it > retains, and the moderation surface that lets you review, restore, and permanently purge it. DevPlace removes records with a **soft delete** rather than a physical delete. Removing something stamps two columns on its row instead of erasing it: - `deleted_at` - an ISO-8601 timestamp marking when the row was removed, - `deleted_by` - who removed it: a user uid, or `system` for automated actions. A live row has `deleted_at = NULL`. Every read path that lists or counts data filters on `deleted_at IS NULL`, so a soft-deleted record disappears from the product immediately yet stays in the database for audit and recovery. Both columns are indexed (`idx__deleted`) to keep the filter fast at scale. ## The two stages of deletion Soft delete pairs with a second stage that frees storage. The platform distinguishes them deliberately: 1. **Removal (soft).** A user or admin removing a post, comment, gist, project, news article, project file, attachment, a vote/reaction/bookmark/follow toggle, a session (logout), a container instance or schedule, a Devii task, lesson, or custom tool, or a per-owner customization. These set `deleted_at` + `deleted_by` and are reversible. 2. **Purge / garbage collection (hard).** Operations whose purpose is to bound storage stay physical deletes, since soft-deleting them would free nothing: async job rows and their staging directories, the container metrics ring buffer, AI usage-ledger retention sweeps and quota resets, expired-session cleanup, fork-rollback of a half-created project, and the news-sync image refresh. The admin **Purge** action below is the manual counterpart that empties the trash. The second stage is what makes the first safe: removals are reversible while retention and ring-buffer mechanics remain hard. ## Toggles revive instead of duplicating Vote, reaction, bookmark, follow, and poll-vote actions are toggles. Toggling off soft-deletes the row; toggling the same target back on **revives the same physical row** (clears `deleted_at`) rather than inserting a second one. Counts and "did I react?" reads filter `deleted_at IS NULL`, so a revived toggle is counted exactly once. ## Cascades share one timestamp When a parent is removed, its children are soft-deleted in the same operation with the **same `deleted_at` timestamp and `deleted_by`**. Deleting a post stamps the post plus its comments, votes, reactions, bookmarks, poll, and attachments with one shared timestamp. That timestamp identifies the whole deletion event, which makes a one-click restore possible. ## Who can do what | Action | Who | Effect | |--------|-----|--------| | Delete content | The owner, or any admin | Soft delete (reversible), cascades to children | | Restore | Admin only | Reverses the entire deletion event | | Purge | Admin only | Permanent hard delete of the event (rows + files) | Members and guests never see the soft-delete, restore, or purge concepts; for them a delete simply removes the item. Recovery is an administrator surface. ## The Trash The **Trash** entry in the admin sidebar opens `/admin/trash`. It lists soft-deleted rows per table (posts, comments, gists, projects, news, project files, attachments), each with its label, the time it was removed, and the resolved `deleted_by` username. Two actions per row: - **Restore** (`POST /admin/trash/{table}/{uid}/restore`) - reads the row's `deleted_at` and reverses every row across all tables sharing that timestamp, bringing the item and everything removed with it back to the live product in one step. - **Purge** (`POST /admin/trash/{table}/{uid}/purge`) - permanently hard-deletes every row sharing that timestamp and unlinks any attachment files or project-file blobs from disk. This cannot be undone and is the only way soft-deleted content is physically removed from the UI. The **Media** trash (`/admin/media`) is the attachment-specific view of the same model; see [Media moderation](/docs/media-moderation.html). ## Security and audit notes - **Accountability.** `deleted_by` records the actor for every removal, including cascades, so the trash doubles as an audit trail of who removed what and when. - **Logout is auditable, not destructive.** Signing out soft-deletes the session row (`deleted_by` is the user) and clears the auth cache, so the session is rejected immediately; expired sessions are still hard-swept by the cleanup path. - **Right-to-be-forgotten.** Bulk "forget" operations (clearing a Devii conversation, forgetting all lessons, resetting a usage quota) remain hard deletes, so a privacy wipe leaves nothing recoverable. - **Purge is irreversible.** It is the only hard delete reachable from the admin UI and deletes files from disk; the confirmation dialog reflects that. - **Retention is unchanged.** Job artifacts, metrics rings, and AI usage ledgers are still pruned on their existing schedules; soft delete does not extend their lifetime. ## Reference - Restore and purge are admin UI endpoints under `/admin/trash`; the member-facing delete endpoints live on each content type's API page. - The data-layer primitives are `soft_delete`, `soft_delete_in`, `restore`, `purge`, `restore_event`, and `purge_event` in the `database` package (`database/soft_delete.py`).