Deleting objects
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 a 403. 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);res, err := native.DeleteObject(ctx, "reports", "q1/summary.txt", "" /* versionID */)
fmt.Println(res.DeleteMarker, res.DeleteMarkerVersionID)// Returns a DeleteResult:
var res = native.deleteObject("reports", "q1/summary.txt");
System.out.println(res.deleteMarker());The same with the S3 client:
await s3.deleteObject("reports", "q1/summary.txt");err := s3.DeleteObject(ctx, "reports", "q1/summary.txt")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 });res, err := native.DeleteObject(ctx, "reports", "q1/summary.txt", versionID)native.deleteObject("reports", "q1/summary.txt", versionId);await s3.deleteObject("reports", "q1/summary.txt", { versionId });err := s3.DeleteObject(ctx, "reports", "q1/summary.txt", lockwellsdk.WithVersionID(versionID))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);out, err := native.BatchDeleteObjects(ctx, "reports", []lockwellnative.BatchDeleteKey{
{Key: "q1/a.txt"},
{Key: "q1/b.txt", VersionID: versionID},
})
for _, d := range out.Deleted {
fmt.Println("deleted", d.Key, d.DeleteMarker)
}
for _, e := range out.Errors {
log.Printf("delete %s failed: %s %s", e.Key, e.Code, e.Message)
}import com.lockwell.sdk.nativeapi.NativeTypes;
import java.util.List;
var out = native.batchDeleteObjects("reports", List.of(
new NativeTypes.ObjectIdentifier("q1/a.txt"),
new NativeTypes.ObjectIdentifier("q1/b.txt", versionId)));
out.deleted().forEach(d -> System.out.println("deleted " + d.key()));
out.errors().forEach(e -> System.out.println("failed " + e.key() + " " + e.code()));::::
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);out, err := s3.DeleteObjects(ctx, "reports", []lockwellsdk.ObjectIdentifier{
{Key: "q1/a.txt"},
{Key: "q1/b.txt", VersionID: versionID},
})
for _, d := range out.Deleted {
fmt.Println("deleted", d.Key, d.DeleteMarker)
}
for _, e := range out.Errors {
log.Printf("delete %s failed: %s %s", e.Key, e.Code, e.Message)
}import com.lockwell.sdk.LockwellClient.ObjectIdentifier;
import java.util.List;
// deleteObjects(bucket, identifiers, quiet); quiet = false here:
var out = s3.deleteObjects("reports", List.of(
new ObjectIdentifier("q1/a.txt"),
new ObjectIdentifier("q1/b.txt", versionId)), false);
out.deleted().forEach(d -> System.out.println("deleted " + d.key()));
out.errors().forEach(e -> System.out.println("failed " + e.key() + " " + e.code()));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.out, err := s3.DeleteObjects(ctx, "reports", ids, lockwellsdk.WithQuietDelete())
// out.Deleted is empty; inspect out.Errors.// The third argument is the quiet flag:
var out = s3.deleteObjects("reports", ids, true);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 state | DeleteObject (no version id) | DeleteObject (with version id) |
|---|---|---|
| Versioning disabled | Removes the object's bytes. | Removes that version (the only one). |
| Versioning enabled | Writes a delete marker; prior versions stay recoverable. | Hard-deletes that exact version. |
| Versioning suspended | Writes 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);// List every version, then batch-delete by version id:
page, _ := native.ListObjectVersions(ctx, lockwellnative.ListObjectVersionsInput{
Bucket: "reports", Prefix: "q1/summary.txt",
})
var ids []lockwellnative.BatchDeleteKey
for _, v := range page.Versions {
ids = append(ids, lockwellnative.BatchDeleteKey{Key: v.Key, VersionID: v.VersionID})
}
_, _ = native.BatchDeleteObjects(ctx, "reports", ids)import com.lockwell.sdk.nativeapi.NativeTypes;
import com.lockwell.sdk.nativeapi.NativeTypes.ListVersionsOptions;
import java.util.ArrayList;
import java.util.List;
var page = native.listObjectVersions("reports",
new ListVersionsOptions("q1/summary.txt", null, null, null));
List<NativeTypes.ObjectIdentifier> ids = new ArrayList<>();
page.versions().forEach(v ->
ids.add(new NativeTypes.ObjectIdentifier(v.key(), v.versionId())));
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);p := s3.NewListObjectVersionsPaginator("reports", lockwellsdk.WithPrefix("q1/summary.txt"))
var ids []lockwellsdk.ObjectIdentifier
for p.HasMorePages() {
page, _ := p.NextPage(ctx)
for _, v := range page.Versions {
ids = append(ids, lockwellsdk.ObjectIdentifier{Key: v.Key, VersionID: v.VersionID})
}
}
_, _ = s3.DeleteObjects(ctx, "reports", ids)import com.lockwell.sdk.LockwellClient.ListVersionsOptions;
import com.lockwell.sdk.LockwellClient.ObjectIdentifier;
import java.util.ArrayList;
import java.util.List;
var pager = s3.listObjectVersionsPaginator("reports",
new ListVersionsOptions().prefix("q1/summary.txt"));
List<ObjectIdentifier> ids = new ArrayList<>();
while (pager.hasMorePages()) {
pager.nextPage().versions().forEach(v ->
ids.add(new ObjectIdentifier(v.key(), v.versionId())));
}
s3.deleteObjects("reports", ids, false);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
- Versioning: list versions and delete markers, restore a deleted object.
- Listing & pagination: gather the keys to batch-delete.
- Object lock: why a retention-locked object refuses deletion.