Lockwell home
Guide

Deleting objects

Delete one object, a specific version, or up to 1000 objects in a batch with Lockwell, and understand how delete markers interact with versioning.

Download .md

Delete one object, a specific version, or up to 1000 objects in a single batch request. On a versioned bucket a delete usually does not remove bytes. It writes a delete marker that hides the object while its prior versions stay recoverable.

This page covers single deletes, batch deletes with per-key results, and how delete interacts with versioning. For the operation matrix, see the native data-plane reference and the S3 operations reference.

Delete one object

DeleteObject removes the current object (on an unversioned bucket) or writes a delete marker (on a versioned one). A delete blocked by Object Lock retention or a legal hold fails with S3 403 AccessDenied; the native API returns 412 with retention_blocked or legal_hold_blocked. The native client reports whether a delete marker was created.

const res = await native.deleteObject("reports", "q1/summary.txt");
console.log(res.deleteMarker, res.versionId);

The same with the S3 client:

await s3.deleteObject("reports", "q1/summary.txt");

A delete is idempotent: deleting a key that is already gone succeeds. Deleting a missing key does not raise a not-found error.

Delete a specific version

Pass a versionId to delete one exact version rather than the current object. On a versioned bucket this is a hard delete of that version (it does not create a delete marker), so use it to prune history or to remove a delete marker and restore the prior version.

await native.deleteObject("reports", "q1/summary.txt", { versionId });
await s3.deleteObject("reports", "q1/summary.txt", { versionId });

Deleting the version id of a delete marker removes the marker, so the prior version becomes current again. List versions first to find the ids; see versioning.

Batch delete

DeleteObjects removes up to 1000 objects in one request. The batch may partially succeed: the server reports per-key successes and per-key failures separately, so a single denied or retention-locked key does not fail the whole call. The SDKs reject an over-1000 batch locally with a clear error before any request goes out.

A batch delete can partially succeed without raising an error. Always walk the errors list to see which keys

failed; the call itself does not throw on a per-key denial.

const res = await native.batchDeleteObjects("reports", ["q1/a.txt", { key: "q1/b.txt", versionId }]);
for (const d of res.deleted) console.log("deleted", d.key, d.deleteMarker);
for (const e of res.errors) console.log("failed", e.key, e.code, e.message);

The S3 batch entries are a key string or { key, versionId }:

const res = await s3.deleteObjects("reports", ["q1/a.txt", { key: "q1/b.txt", versionId }]);
for (const d of res.deleted) console.log("deleted", d.key, d.deleteMarker);
for (const e of res.errors) console.log("failed", e.key, e.code, e.message);

Quiet mode (S3 client)

By default the S3 batch response lists every successfully deleted key. Quiet mode suppresses those success entries and returns only the failures, which keeps the response small when you are deleting thousands of keys and only care about what went wrong. The server still deletes every key; only the response shrinks. Errors are always returned regardless of quiet mode.

const res = await s3.deleteObjects("reports", keys, { quiet: true });
// res.deleted is empty; res.errors holds any per-key failures.

Per-key results

Each successful entry reports the key, and on a versioned bucket whether the delete created a delete marker plus that marker's version id. Each failed entry reports the key, an error code, and a message. Walk both lists after a batch:

const res = await native.batchDeleteObjects("reports", keys);
if (res.errors.length > 0) {
  // Retry or surface the failures; the rest were deleted.
  for (const e of res.errors) console.error(`${e.key}: ${e.code} ${e.message}`);
}

A retention-locked or legal-held key shows up in errors (forbidden), not as a thrown call error, so one protected object never blocks the rest of the batch.

Delete and versioning

What a delete does depends on the bucket's versioning state:

Bucket stateDeleteObject (no version id)DeleteObject (with version id)
Versioning disabledRemoves the object's bytes.Removes that version (the only one).
Versioning enabledWrites a delete marker; prior versions stay recoverable.Hard-deletes that exact version.
Versioning suspendedWrites a null-version delete marker; existing versions stay.Hard-deletes that version.

After a delete-marker delete, a plain GetObject returns not-found (the marker hides the object), but the prior version is still readable by its version id and recoverable by deleting the marker. To wipe an object and all its history, delete every version id (list versions, then batch-delete with version ids).

// Wipe a key and all its versions on a versioned bucket:
const ids = [];
for await (const page of native.paginateObjectVersions("reports", { prefix: "q1/summary.txt" })) {
  for (const v of page.versions) ids.push({ key: v.key, versionId: v.versionId });
}
await native.batchDeleteObjects("reports", ids);
// Same wipe with the S3 client's version paginator:
const ids = [];
for await (const page of s3.paginateObjectVersions("reports", { prefix: "q1/summary.txt" })) {
  for (const v of page.versions) ids.push({ key: v.key, versionId: v.versionId });
}
await s3.deleteObjects("reports", ids);

Deleting buckets

DeleteBucket removes an empty bucket. The server rejects a delete of a bucket that still holds objects (or, on a versioned bucket, versions or delete markers), so empty it first with a listing-driven batch delete. Bucket lifecycle is covered in Versioning and the SDK references.

Next steps

On this page