---
title: "Retention"
description: "The one setting that bounds what you store, fixed when the backup is created."
url: "https://saved.sh/docs/concepts/retention"
---

**Retention decides how long artifacts live.** It is the only setting that bounds what you
store, and therefore the only one that bounds what you pay to hold.

A backup with no retention **keeps everything forever**. That is the default, and it is the
right default, but it is not free.

## The rule [#the-rule]

An artifact is expired when it is **both**:

1. older than `expire_after`, **and**
2. outside the newest `keep_last`.

```yaml
retention:
  expire_after: 90d    # nothing older than 90 days
  keep_last: 10        # but always keep the 10 most recent, however old
```

The two combine with **and**, never with or.

| Field          | On its own, deletes                    | Is                              |
| -------------- | -------------------------------------- | ------------------------------- |
| `expire_after` | Everything older, subject to the floor | The age limit                   |
| `keep_last`    | **Nothing**                            | A floor, exempting the newest N |
| `lock_for`     | Nothing. It only forbids               | A protection window             |

## `keep_last` is a floor, not a cap [#keep_last-is-a-floor-not-a-cap]

This is the most misread thing in the product.

**`keep_last: 5` on its own keeps every artifact forever**, exactly as if retention were off.
It does not mean "keep only five".

The reason is a failure we would rather not ship. Read as a cap, `keep_last: 5` deletes your
sixth-newest artifact the moment a seventh appears. A source that suddenly runs more often
than expected, or a burst of manual triggers during an incident, would quietly destroy every
copy older than this afternoon. A floor cannot do that.

There is no hard cap on count, deliberately. Bound by age, and use `keep_last` to stop the age
bound biting during a quiet period.

## Two things are never deleted [#two-things-are-never-deleted]

Whatever the policy says:

* **The newest remaining artifact.** A backup that has run at least once always has something
  to restore from.
* **Any artifact still inside its lock window.**

## Retention is write-once [#retention-is-write-once]

**Fixed when you define the backup. Not editable afterwards**, not loosened, not tightened,
not turned off. A different policy means a new backup.

<Callout type="error">
  Decide retention before the first run. It is one of only two fields you cannot change later,
  the other being [kind](/docs/concepts/backups#kind-decides-who-holds-your-credentials).
</Callout>

That constraint is the point rather than a limitation. The retention shown on a backup is the
promise made to artifacts **already archived under it**. A number you could quietly raise next
quarter, after an auditor asked, would not be a promise. Making it immutable is what makes it
worth reading.

<Callout type="warn">
  Trial workspaces cap retention at **7 days**. Because retention is write-once, a backup
  created on trial keeps that policy even after you upgrade. If you intend a long retention,
  set it on a paid plan.
</Callout>

## Protection: `lock_for` [#protection-lock_for]

`lock_for` marks new artifacts as protected for a window, during which our delete path and our
retention sweep both refuse to remove them.

| Property   | Behaviour                                |
| ---------- | ---------------------------------------- |
| Applied    | Per artifact, at archive time            |
| Affects    | Future artifacts only. Never retroactive |
| Expiry     | Lapses on its own                        |
| Constraint | May never exceed `expire_after`          |

A policy that forbids a deletion it also requires is a contradiction, and is refused at
definition.

<Callout type="error">
  **What a lock actually stops.** It is an application-level hold: it stops deletion through our
  API, our dashboard, our support tooling and our retention sweep. It is **not** object-lock
  enforcement at the storage layer, so it is not a defence against somebody holding the archive
  credentials directly. Treat it as protection against mistake and misuse, not as a
  compliance-grade WORM guarantee.
</Callout>

If you need storage-layer immutability, [deliver to your own bucket](/docs/backups/delivery)
and configure object lock there, where enforcement is under your control.

## The sweep [#the-sweep]

Expiry is done by a **daily sweep**, not at run time. Nothing in the backup path deletes
anything.

* **Expiry is not instant.** "90 days" is 90 days plus up to one day.
* **A backup with no `expire_after` is skipped entirely.** Its artifacts are never examined.
* **In your own buckets**, the sweep deletes only where you granted `allowDelete`, which is
  off by default. With it off, that bucket grows forever and clearing it is your job.

## Worked examples [#worked-examples]

A daily backup, one year in, on day 366.

| Policy                               | Kept                                            |
| ------------------------------------ | ----------------------------------------------- |
| none                                 | All 365                                         |
| `keep_last: 10`                      | All 365. Nothing expires without `expire_after` |
| `expire_after: 90d`                  | The newest 90                                   |
| `expire_after: 90d`, `keep_last: 10` | The newest 90. The floor is already satisfied   |
| `expire_after: 7d`, `keep_last: 30`  | The newest 30. The floor binds                  |

The last row is where `keep_last` earns its place: when the age limit is tighter than the
number of copies you want on hand.

## Next [#next]

<Cards>
  <Card href="/docs/backups/retention" title="Retention in depth" description="Durations, the sweep, and your own buckets." />

  <Card href="/docs/concepts/quotas" title="Quotas" description="The other thing that bounds storage." />

  <Card href="/docs/billing/usage" title="Usage" description="What holding an artifact costs." />
</Cards>
