> Every UQL docs page, as Markdown: https://uql-orm.dev/llms.txt
> The same docs over MCP: https://uql-orm.dev/mcp
> Before writing UQL code, read the skill: https://uql-orm.dev/.well-known/agent-skills/uql-orm/SKILL.md

# Soft Delete

> Mark rows deleted instead of removing them, keep them out of every query by default, and restore them later.

Source: https://uql-orm.dev/entities/soft-delete

A soft delete stamps a row as deleted instead of removing it. The row drops out of queries, but you can still restore or audit it, and rows pointing at it stay valid. Mark one field `softDelete` to enable it:

```ts
import { Entity, Id, Field } from 'uql-orm';

@Entity()
export class User {
  @Id({ type: Number }) id?: number;
  @Field({ type: String }) name?: string | null;
  @Field({ type: 'timestamptz', softDelete: true }) deletedAt?: Date | null;
}
```

`softDelete: true` stamps `new Date()`. To stamp something else, pass a value, SQL such as `currentTimestamp`, or a callback, like `() => Date.now()` for an epoch-millis column. An entity has at most one soft-delete field.

## Deleting and reading

A delete becomes an update that stamps only rows not already deleted:

```ts title="You write"
await pool.deleteOneById(User, 1);
```

PostgreSQL:

```sql
UPDATE "User" SET "deletedAt" = $1 WHERE "id" = $2 AND "deletedAt" IS NULL
```

Every read leaves deleted rows out, through the built-in `softDelete` [query filter](https://uql-orm.dev/querying/filters.md), which combines with any filters you define:

```ts title="You write"
const users = await pool.findMany(User, { $select: { id: true, name: true } });
```

PostgreSQL:

```sql
SELECT "id", "name" FROM "User" WHERE "deletedAt" IS NULL
```

## Reading deleted rows

The filter steps aside when your `$where` names the field, so reading only deleted rows is a plain query, which works over HTTP too. To read live and deleted rows together, pass `withDeleted()`, a server-side option that never travels over the wire. It covers the read’s own rows; populated relations still hide their deleted ones. Given options, `withDeleted(opts)` returns them with soft-deleted rows let in.

```ts
import { withDeleted } from 'uql-orm';

const trashed = await pool.findMany(User, {
  $where: { deletedAt: { $ne: null } },
});
const all = await pool.findMany(User, {}, withDeleted());
```

## Restoring and hard deletes

`restoreOneById` and `restoreMany` set the field back to `null`, finding deleted rows despite the filter. `{ hardDelete: true }` removes rows for good, already-deleted ones included:

```ts
await pool.restoreMany(User, { $where: { companyId: 5 } });
await pool.deleteOneById(User, 1, { hardDelete: true });
```

A soft delete [cascades](https://uql-orm.dev/entities/relations.md#writing-related-rows-cascade) only to related entities that also soft-delete, and keeps the rest. A restore does not cascade, so restore related rows yourself.
