Object versioning
Keep every version of your objects and recover from accidental deletions or overwrites in any OtterStorage bucket.
Versioning makes OtterStorage keep every revision of your objects instead of overwriting them. When it's enabled, overwriting or deleting a key doesn't destroy its previous content: every change is stored under a version identifier (versionId), so you can always go back to a previous state. It's the best defense against accidental deletions and overwrites.
What it is and why to use it
Without versioning, every time you upload an object with a key that already exists, the new content replaces the previous one and the original is lost. With versioning enabled, OtterStorage keeps the previous version and creates a new "current" version; the older ones become noncurrent versions that you can list and restore.
Some reasons to enable it:
- Recovery from mistakes: undo an accidental overwrite or deletion by restoring the previous version.
- Change history: keep a record of how each object has evolved over time.
- Protection against faulty applications: a process that overwrites data by mistake doesn't destroy anything irreversibly.
- Foundation for retention and compliance: it pairs well with Legal Hold to preserve the full history.
With OtterStorage, we don't charge for requests or deletions, so enabling versioning adds no per-operation cost: you only pay for the storage taken up by the versions you keep.
Enabling and suspending versioning
The versioning state is controlled at the bucket level with the put-bucket-versioning operation. It accepts two states: Enabled and Suspended. All the examples use aws s3api pointed at the OtterStorage endpoint; see how to configure the AWS CLI if you haven't done so yet.
To enable versioning on the bucket mi-bucket:
aws s3api put-bucket-versioning \
--endpoint-url https://es-mad-1.s3.otterstorage.io \
--region eu-mad \
--bucket mi-bucket \
--versioning-configuration Status=Enabled
To suspend it, use the same command with Status=Suspended:
aws s3api put-bucket-versioning \
--endpoint-url https://es-mad-1.s3.otterstorage.io \
--region eu-mad \
--bucket mi-bucket \
--versioning-configuration Status=Suspended
Suspending versioning does not delete the existing versions: the ones you already had are kept and remain recoverable. The only thing that changes is that, from that point on, new writes stop generating new versions (the versions that are created will carry the special identifier null). To resume the behavior, set Status=Enabled again.
Check the current state
Verify which state a bucket is in with get-bucket-versioning:
aws s3api get-bucket-versioning \
--endpoint-url https://es-mad-1.s3.otterstorage.io \
--region eu-mad \
--bucket mi-bucket
The response is a small JSON with the current state:
{
"Status": "Enabled"
}
If the bucket has never had versioning enabled, the response comes back empty (no Status field), which is equivalent to "not versioned".
How it behaves with versioning enabled
Once enabled, OtterStorage changes the way it handles writes and deletions:
On overwrite: the previous version is kept
When you upload an object with a key that already exists, the previous version is not lost: it becomes a noncurrent version and the new one becomes the current version. Each PutObject returns the assigned versionId:
aws s3api put-object \
--endpoint-url https://es-mad-1.s3.otterstorage.io \
--region eu-mad \
--bucket mi-bucket \
--key informe.pdf \
--body informe-v2.pdf
{
"ETag": "\"9b2cf5...\"",
"VersionId": "3sL0nD02pX9aQ7Kf2bVtY8mZ1cR4wH6"
}
Each time you repeat the upload to the same key, you'll get a different versionId, and all previous versions will remain available to list or restore.
On delete: a delete marker is created
A "normal" deletion (without specifying a version) does not remove the data: OtterStorage places a delete marker as the new current version. The object appears to disappear in a simple listing, but all of its real versions are still there, intact, behind the marker.
aws s3api delete-object \
--endpoint-url https://es-mad-1.s3.otterstorage.io \
--region eu-mad \
--bucket mi-bucket \
--key informe.pdf
{
"DeleteMarker": true,
"VersionId": "Hk7Vd0pQ2aN8sLm4wR1bX6cT9zF3yJ5"
}
The returned VersionId identifies the delete marker itself. Remember that deletions are not billed in OtterStorage, so this operation has no per-request cost.
Recover a version
There are two typical recovery situations: retrieving a specific version of an overwritten object, or "reviving" an object that was deleted with a delete marker.
Download a specific version
Use get-object with --version-id to retrieve exactly the revision you need:
aws s3api get-object \
--endpoint-url https://es-mad-1.s3.otterstorage.io \
--region eu-mad \
--bucket mi-bucket \
--key informe.pdf \
--version-id 3sL0nD02pX9aQ7Kf2bVtY8mZ1cR4wH6 \
informe-recuperado.pdf
To restore that version as the current one, upload it again with put-object: a new version will be created with the old content, which then becomes the current version.
Revive a deleted object
If the object "disappeared" because it has a delete marker on top of it, simply delete the delete marker. To do so, run delete-object specifying the --version-id of the marker (the one you saw when deleting, or the one that appears when listing versions):
aws s3api delete-object \
--endpoint-url https://es-mad-1.s3.otterstorage.io \
--region eu-mad \
--bucket mi-bucket \
--key informe.pdf \
--version-id Hk7Vd0pQ2aN8sLm4wR1bX6cT9zF3yJ5
When you remove the marker, the version underneath becomes current again and the object reappears in normal listings. Like any deletion in OtterStorage, this operation is not billed.
List all versions
A normal listing (list-objects-v2) only shows the current version of each key. To see the full history—including noncurrent versions and delete markers—use list-object-versions:
aws s3api list-object-versions \
--endpoint-url https://es-mad-1.s3.otterstorage.io \
--region eu-mad \
--bucket mi-bucket \
--prefix informe.pdf
The response separates the real versions (Versions) from the delete markers (DeleteMarkers). The IsLatest field indicates which is the current version of each key:
{
"Versions": [
{
"Key": "informe.pdf",
"VersionId": "3sL0nD02pX9aQ7Kf2bVtY8mZ1cR4wH6",
"IsLatest": false,
"LastModified": "2026-06-09T10:14:22.000Z",
"Size": 184320
},
{
"Key": "informe.pdf",
"VersionId": "Qp8Wb1nM5tK2vL9cR3aX7dZ4yH6sF0jB",
"IsLatest": false,
"LastModified": "2026-06-08T17:03:51.000Z",
"Size": 180224
}
],
"DeleteMarkers": [
{
"Key": "informe.pdf",
"VersionId": "Hk7Vd0pQ2aN8sLm4wR1bX6cT9zF3yJ5",
"IsLatest": true,
"LastModified": "2026-06-10T08:41:09.000Z"
}
]
}
With these versionId values you can download any specific revision or delete the delete marker to recover the object, as shown above.
Combine with lifecycle to expire old versions
Keeping all versions forever ends up taking storage you may not need. The recommended solution is to use lifecycle rules to automatically expire noncurrent versions after a certain time, always keeping the current version intact.
The NoncurrentVersionExpiration rule defines how many days a version should be kept after it stops being the current one. For example, to delete noncurrent versions 30 days after they become obsolete:
aws s3api put-bucket-lifecycle-configuration \
--endpoint-url https://es-mad-1.s3.otterstorage.io \
--region eu-mad \
--bucket mi-bucket \
--lifecycle-configuration '{
"Rules": [
{
"ID": "expirar-versiones-antiguas",
"Status": "Enabled",
"Filter": { "Prefix": "" },
"NoncurrentVersionExpiration": {
"NoncurrentDays": 30
}
}
]
}'
With this rule, the current version of each object is never touched, but the old revisions clean themselves up 30 days after becoming obsolete. Because the deletions performed by lifecycle are also not billed in OtterStorage, this cleanup adds no per-operation cost: you only reduce what you pay in storage. For more advanced rules (also expiring delete markers, different prefixes, etc.), see the lifecycle guide.
Best practices
- Enable it early: versioning only protects changes that happen after it's enabled. Do it when you create the bucket.
- Set limits with lifecycle: use
NoncurrentVersionExpirationso the history doesn't grow uncontrollably. - Suspend, don't improvise: if you need to stop versioning, use
Status=Suspended; your previous versions will still be recoverable. - Combine it with retention: for compliance scenarios, add Legal Hold and preserve the bucket's full history.
For more details on buckets, access keys, and the rest of the operations, see the documentation.
Ready to try it out?
Create your account and get your keys in minutes.