Object Versioning in Cloud Storage: How It Works, Recovery, and Cost Control

Object versioning in Cloud Storage preserves previous copies of objects when they are overwritten or deleted. Instead of losing the old content permanently, Cloud Storage keeps it as a noncurrent version you can retrieve at any time. It is the simplest protection against accidental data loss inside a bucket, but it comes with a cost trade-off: every noncurrent version is billed at the same rate as a live object, so versioning must be paired with lifecycle rules.

This page explains how versioning works under the hood, how generation numbers identify individual versions, what actually happens when you overwrite or delete an object, how to recover from either, how to keep costs under control, and when versioning is the right choice versus when you need something stronger.

Simple explanation

By default in Cloud Storage, uploading a file with the same name as an existing one permanently replaces it. The old content is gone. There is no undo.

When you enable versioning on a bucket, that behaviour changes. Uploading a new version of a file keeps the previous copy in storage. The old copy becomes a noncurrent version. It is invisible to normal reads (requests still return the latest version), but it is there and recoverable. The same applies to deletions: deleting an object in a versioned bucket does not destroy the content. It creates a delete marker on top, and the content survives as a noncurrent version until you explicitly remove it.

Analogy

Versioning works like revision history in a document editor. When you save a new version, the previous one is not deleted. It sits in the history list with a timestamp and version number. You can retrieve, compare, or restore it at any time. The current document is what you see by default. Deleting the document archives it from your view but leaves the revision history intact until you explicitly purge it.

How versioning works in Google Cloud Storage

Versioning is configured at the bucket level. You either enable it or leave it off (the default). All objects in the bucket are subject to the same setting; you cannot enable it for individual objects only.

Live versions and noncurrent versions

At any point in time, an object has one live version: the current content, returned by default on any read. When versioning is enabled and you overwrite that object, the new content becomes the live version and the previous content becomes a noncurrent version. Multiple noncurrent versions can accumulate if an object is overwritten repeatedly.

Generation numbers

Every object version has a generation number: a 16-digit integer derived from the time the object was created (in microseconds since the Unix epoch). When you overwrite an object, the new content gets a new generation number and the previous content keeps its original one, becoming noncurrent. Generation numbers are how you identify and address specific versions in CLI commands and API calls.

What happens on overwrite

When you upload a file that already exists in a versioned bucket, Cloud Storage assigns a new generation number to the incoming content and marks it as the live version. The previous live version becomes noncurrent, keeps its generation number, and continues to be stored and billed.

What happens on delete

Deleting an object in a versioned bucket does not remove the content. Cloud Storage creates a delete marker: a special entry with a new generation number and no content body. Normal requests return a 404 because the delete marker is now the live version. The previous content survives as a noncurrent version with its original generation number and is fully recoverable.

Common misconception

Running gcloud storage rm gs://my-bucket/file.json on a versioned bucket does not permanently delete the object. It only creates a delete marker. The previous content remains in storage and continues to incur charges. To permanently remove an object, you must specify its generation number or use —all-versions.

What changes when you overwrite or delete an object

Here is a concrete example using a file called config.json.

Versioning off (default)

  • You upload config.json. It is stored as generation 1709900000000000.
  • You upload a new config.json. The old content is permanently replaced. Generation 1709900000000000 is gone. No recovery.
  • You delete config.json. The object is permanently removed. No recovery.

Versioning on

  • You upload config.json. It is stored as generation 1709900000000000 (live).
  • You upload a new config.json. It is stored as generation 1709900000111111 (live). Generation 1709900000000000 becomes noncurrent. Both are stored and billed.
  • You delete config.json without specifying a generation. A delete marker is created as generation 1709900000222222. Normal requests return 404. Generations 1709900000000000 and 1709900000111111 remain as noncurrent versions and are recoverable.
  • To permanently delete: you must delete each version by generation number, or use —all-versions.

How to enable, check, and suspend versioning

Versioning can be enabled or suspended at any time on an existing bucket. Suspending stops new noncurrent versions from being created going forward, but does not delete any versions already present.

# Enable versioning on a bucket
gcloud storage buckets update gs://my-app-data \
  --versioning

# Check whether versioning is enabled
gcloud storage buckets describe gs://my-app-data \
  --format="value(versioning)"

# Suspend versioning (existing noncurrent versions are kept)
gcloud storage buckets update gs://my-app-data \
  --no-versioning
Best practice

Set up your lifecycle rules for noncurrent versions at the same time as you enable versioning. Waiting until later means weeks of unchecked version accumulation, especially on active buckets. See the Object Lifecycle Management guide for the full rule syntax.

Working with object versions

To interact with a specific version, use its generation number as part of the object reference. You retrieve generation numbers with gcloud storage ls —all-versions.

# List all versions of every object in a bucket (including noncurrent)
gcloud storage ls --all-versions gs://my-app-data/

# List all versions of a specific object
gcloud storage ls --all-versions gs://my-app-data/config.json

# Download a specific noncurrent version to a local file
gcloud storage cp \
  "gs://my-app-data/config.json#1709900000000000" \
  ./config-previous.json

# Restore a previous version by copying it back over the live version
gcloud storage cp \
  "gs://my-app-data/config.json#1709900000000000" \
  gs://my-app-data/config.json

# Delete a specific noncurrent version permanently
gcloud storage rm \
  "gs://my-app-data/config.json#1709900000000000"

# Delete all versions of an object at once (live + all noncurrent)
gcloud storage rm --all-versions gs://my-app-data/config.json
Note

Generation numbers in these examples are illustrative. Real generation numbers are 16-digit integers. Always retrieve the exact number with gcloud storage ls —all-versions before referencing a specific version in a command.

Storage costs and lifecycle rules

Cost risk

Every noncurrent version is billed at the same rate as a live object, regardless of storage class. Noncurrent versions never expire automatically. Without lifecycle rules, an active bucket with versioning enabled can accumulate costs far beyond what you expect.

If you have a 1 GB object and overwrite it ten times, you have up to 11 GB billed even though only 1 GB is the current version. Always pair versioning with lifecycle rules that clean up noncurrent versions. Two patterns work well together:

  • Time-based: delete noncurrent versions older than 30 days.
  • Count-based: keep only the last 3 versions of any object.
{
  "lifecycle": {
    "rule": [
      {
        "condition": {
          "isLive": false,
          "age": 30
        },
        "action": {
          "type": "Delete"
        }
      },
      {
        "condition": {
          "isLive": false,
          "numNewerVersions": 3
        },
        "action": {
          "type": "Delete"
        }
      }
    ]
  }
}

The first rule deletes noncurrent versions older than 30 days. The second deletes noncurrent versions when three or more newer versions of the same object already exist. Together they ensure you never keep more than 3 historical versions and never keep any version longer than 30 days.

For broader storage cost control across all your buckets, see Storage Classes in GCP for how class choice affects the cost of both live and noncurrent versions.

When to use versioning

Versioning is a good fit when accidental overwrite or delete is a realistic risk and you want a low-friction recovery path within the same bucket:

  • Configuration files. Application config or deployment manifests that are updated frequently. A bad push is immediately recoverable by restoring the previous generation.

  • User-uploaded assets. Images, documents, or media files that users can replace. Keeps the previous upload available if the replacement turns out to be wrong or corrupted.

  • Data pipeline outputs. Jobs that write results to the same object path on each run. Versioning gives you the previous run’s output without renaming the object on every write.

  • Shared team buckets. Where multiple people or services write to the same paths and accidental overwrites are a known risk.

  • Basic rollback safety. Any scenario where “undo the last write” is needed without building a separate backup pipeline.

When not to rely on versioning alone

Versioning is a within-bucket, object-level protection. It does not cover everything:

  • Disaster recovery. Versioning does not create copies in a separate location or project. If the bucket is deleted or the project is compromised, all versions are lost. Use multi-regional storage or cross-project replication for geographic resilience.

  • Admin-level destructive actions. An IAM principal with storage.buckets.delete or storage.objects.delete permission on all versions can remove everything. Versioning does not restrict what an admin can do. Proper IAM controls are essential alongside it.

  • Compliance or legal retention. Versioning does not prevent deletion by authorised users or automated lifecycle rules. For records that must be preserved for a defined minimum period, use retention policies or object holds instead.

  • Cross-project isolation. True backup means a separate copy in a separate place with separate access controls. Versioning lives in the same bucket, accessible via the same credentials.

Versioning is not backup

If an admin deletes the bucket, or a runaway lifecycle rule removes all objects, or credentials are compromised and an attacker runs —all-versions on every object, versioning provides no protection. For genuine backup, copy data to a separate bucket in a separate project with separate IAM controls.

Versioning vs retention policies vs backup

These three controls are complementary, not interchangeable. Each has a distinct job:

ControlWhat it doesWhat it does not do
Object versioningPreserves previous copies on overwrite or delete. Enables recovery by generation number.Does not prevent deletion by an authorised user. Does not isolate data from the source bucket.
Retention policySets a minimum retention period. No object can be deleted before the period has elapsed.Does not preserve previous versions after the period ends. Does not protect against bucket-level deletion by a project owner.
Object holdsLocks individual objects from deletion until the hold is explicitly released. Used for legal preservation.Does not apply automatically to all objects. Requires manual or programmatic release.
Backup (separate bucket)Independent copy in a separate bucket with separate IAM and access paths. Protects against bucket deletion, credential compromise, and operator error.Requires an explicit copy operation. Not automatic unless you build it.

For most applications, the right combination is versioning (day-to-day overwrite protection) plus lifecycle rules (to prevent unbounded cost growth) plus a separate backup bucket (for scenarios versioning cannot cover). For regulated data, add retention policies or object holds on top. See the Cloud Storage Security guide for how retention and holds work in detail.

Common mistakes

  1. Enabling versioning without lifecycle rules for noncurrent versions. Every overwrite silently creates a new billable version. On an active bucket, storage costs can be several times higher than expected within weeks. Always define a lifecycle rule to expire noncurrent versions when you enable versioning.

  2. Assuming a plain gcloud storage rm permanently deletes an object. On a versioned bucket, deleting without a generation number creates a delete marker. The object appears gone, but the previous content remains as a noncurrent version and continues to be billed. To permanently remove an object, use —all-versions or specify each generation number explicitly.

  3. Treating versioning as a backup strategy. Versioning keeps previous versions within the same bucket. It does not protect against someone deleting the bucket, a destructive lifecycle rule, or a service account with full bucket admin rights. For genuine backup, copy data to a separate bucket in a separate project with separate IAM.

  4. Suspending versioning and assuming existing noncurrent versions disappear. Suspending stops new noncurrent versions from being created but does not delete the ones already there. Run gcloud storage ls —all-versions to audit what remains after suspending, and delete unwanted versions manually or via lifecycle rules.

  5. Not monitoring storage growth after enabling versioning. Buckets with versioning enabled can grow much faster than expected on objects that are overwritten frequently. Set up billing alerts and check storage usage regularly.

  6. Enabling versioning on every bucket regardless of need. Versioning is not free. For buckets holding infrequently updated or throwaway data, accumulating noncurrent versions adds cost without real value. Enable it where accidental overwrite is a genuine risk.

Frequently asked questions

What is the difference between a live version and a noncurrent version?

The live version is the current object — the one Cloud Storage returns when you request it without a generation number. A noncurrent version is any earlier copy: one replaced by a newer upload, or that has a delete marker placed on top of it. Noncurrent versions remain in storage until a lifecycle rule or an explicit deletion removes them. Both live and noncurrent versions are billed at the same storage rate.

Does Cloud Storage versioning increase costs?

Yes. Every noncurrent version occupies storage and is billed at the same rate as a live object. If you overwrite a 500 MB file ten times, you have up to 5.5 GB billed unless lifecycle rules clean up the old versions. Always pair versioning with a lifecycle rule that expires noncurrent versions on a schedule that fits your recovery window.

Can I recover a deleted object with versioning enabled?

Yes, as long as the noncurrent version has not itself been deleted. Deleting an object in a versioned bucket creates a delete marker rather than permanently removing the content. The previous content remains as a noncurrent version identified by its generation number. To restore it, copy that version back over the current object name using gcloud storage cp with the generation number.

Is versioning the same as backup?

No. Versioning preserves previous versions of objects within the same bucket. It does not protect against bucket deletion, a compromised admin account deleting everything, or a misconfigured lifecycle rule destroying all objects. True backup means copying data to a separate bucket in a separate project with separate IAM controls and access paths.

What happens if I suspend versioning on a bucket?

Suspending versioning stops Cloud Storage from creating new noncurrent versions from that point forward. Existing noncurrent versions are not deleted — they remain in storage and continue to incur charges until a lifecycle rule or explicit deletion removes them. Use gcloud storage ls --all-versions after suspending to audit what versions remain.

Last verified: 23 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.