> ## Documentation index
> Fetch the complete documentation index at: https://upstash.com/docs/llms.txt
> Ask it with GET https://upstash.com/docs/ask?q=<keywords or question>. Short keywords rank best.
> Use these to discover all available pages before exploring further.
> Feature requests: https://upstash.com/docs/common/help/feature-requests.md lists what users asked for and how to vote for a request or add one.
> Page rating: `GET https://upstash.com/docs/feedback?path=/docs/blob/bucket/deleting&helpful=true` (or `false`) records whether this page helped. An optional `comment` (URL-encoded, at most 500 characters) says what was missing or wrong. It is anonymous and needs no key. Comments hold no code, secrets or personal data.

# Deleting

> Delete one path, a list or a prefix, and clear out incomplete multipart uploads.

This page covers deleting one path, a list of paths, or everything under a prefix, and the calls that clean up multipart uploads that were never completed.

<CodeGroup>
```ts TypeScript
await bucket.del("avatars/me.png")                   // one path
await bucket.del(["a.png", "b.png", "c.png"])        // an array of paths
await bucket.del({ prefix: "tmp/" })                 // everything under a prefix
```

```python Python
bucket.delete("avatars/me.png")                  # one path
bucket.delete(["a.png", "b.png", "c.png"])       # a list of paths
bucket.delete(prefix="tmp/")                      # everything under a prefix
```

```go Go
// one path
bucket.Delete(ctx, "avatars/me.png")
// a slice of paths
bucket.DeleteMany(ctx, []string{"a.png", "b.png", "c.png"})
// everything under a prefix
bucket.DeletePrefix(ctx, "tmp/")
```
</CodeGroup>

All three shapes return nothing and treat "already gone" as success. They differ in how many requests they make and what they throw when storage refuses part of the work. Method signatures and the target type: [TypeScript](/blob/sdks/ts/bucket#del), [Python](/blob/sdks/py/bucket#delete), [Go](/blob/sdks/go/bucket#delete).

A target that is none of the three shapes is refused with `invalid_input`. In Python that is passing both or neither of paths and `prefix=`; any other iterable, a dict included, is read as a list of paths. Go has a method per shape, so there is no target to get wrong. A path with a `.` or `..` segment is rejected before any request is sent; see [Paths](/blob/bucket/writing#paths).

The upload handler also deletes on its own: a throw out of the completion callback, or a cancel from the browser, removes the object that upload wrote. See [Recording the upload](/blob/uploads/upload-handler#recording-the-upload).

---

## One path

<CodeGroup>
```ts TypeScript
await bucket.del("drafts/9f3c.txt")
await bucket.del("drafts/9f3c.txt")  // still no throw
```

```python Python
bucket.delete("drafts/9f3c.txt")
bucket.delete("drafts/9f3c.txt")  # still no raise
```

```go Go
bucket.Delete(ctx, "drafts/9f3c.txt")
bucket.Delete(ctx, "drafts/9f3c.txt") // still nil
```
</CodeGroup>

One `DELETE` request. A 404 counts as success, so a delete is safe to run from a retried job or an at-least-once queue consumer. Any other failure throws; see [Errors](/blob/reference/errors#what-storage-errors-map-to).

A delete never says whether anything was there. To find out, call `exists` first.

---

## A list of paths

<CodeGroup>
```ts TypeScript
try {
  await bucket.del(paths)
} catch (e) {
  if (BlobError.is(e) && e.code === "partial_delete") {
    await requeue(e.failed ?? [])  // the paths still in the bucket, verified one by one
    return
  }
  throw e
}
```

```python Python
try:
    bucket.delete(paths)
except BlobError as e:
    if e.code != "partial_delete":
        raise
    requeue(e.failed or [])  # the paths still in the bucket, verified one by one
```

```go Go
err := bucket.DeleteMany(ctx, paths)
var be *blob.Error
if errors.As(err, &be) && be.Code == blob.CodePartialDelete {
	// the paths still in the bucket, verified one by one
	return requeue(be.Failed)
}
if err != nil {
	return err
}
```
</CodeGroup>

Sent as batch deletes in chunks of 1000 paths, so a 5000-path list is five requests. In TypeScript a bad path fails the chunk it is in, and earlier chunks have already run. Python and Go check every path before the first request, so nothing is deleted.

When storage reports keys as failed, the SDK re-checks each one and keeps only the paths still there. If any survive, the delete throws `partial_delete` with them as its failed paths. Everything not in that list was deleted. To recover, retry with those paths.

---

## A prefix

<CodeGroup>
```ts TypeScript
await bucket.del({ prefix: "users/7/tmp/" })
```

```python Python
bucket.delete(prefix="users/7/tmp/")
```

```go Go
bucket.DeletePrefix(ctx, "users/7/tmp/")
```
</CodeGroup>

Pages through `list` at 1000 objects per page and batch-deletes each page as it goes. A prefix with 100,000 objects is 100 list requests and 100 batch deletes, run one after another. It is not atomic; objects written under the prefix while it runs may or may not be caught.

Failures work as for a list. Survivors from every page are collected and thrown as `partial_delete`.

<Warning>
An empty prefix would match every object in the bucket, so it is refused with `invalid_input`. This protects against an unset variable or an empty form field. To wipe the bucket on purpose, say so with the all flag, or in Go with `DeleteAll`:

<CodeGroup>
```ts TypeScript
await bucket.del({ prefix: "", all: true })
```

```python Python
bucket.delete(prefix="", all=True)
```

```go Go
bucket.DeleteAll(ctx)
```
</CodeGroup>

The all flag is only consulted for the empty prefix.
</Warning>

---

## `move` leaves a copy on failure

<CodeGroup>
```ts TypeScript
try {
  await bucket.move("tmp/9f3c", "avatars/7.png")
} catch (e) {
  if (BlobError.is(e) && e.code === "move_left_a_copy") {
    // avatars/7.png exists and is correct. tmp/9f3c is also still there.
    await bucket.del("tmp/9f3c")  // retry the source delete, not the move
    return
  }
  throw e
}
```

```python Python
try:
    bucket.move("tmp/9f3c", "avatars/7.png")
except BlobError as e:
    if e.code != "move_left_a_copy":
        raise
    # avatars/7.png exists and is correct. tmp/9f3c is also still there.
    bucket.delete("tmp/9f3c")  # retry the source delete, not the move
```

```go Go
_, err := bucket.Move(ctx, "tmp/9f3c", "avatars/7.png", nil)
if blob.HasCode(err, blob.CodeMoveLeftACopy) {
	// avatars/7.png exists and is correct. tmp/9f3c is also still there.
	// retry the source delete, not the move
	return bucket.Delete(ctx, "tmp/9f3c")
}
if err != nil {
	return err
}
```
</CodeGroup>

`move` is a copy followed by a delete, because storage has no rename. If the copy fails, nothing changed and you get the copy's error. If the copy succeeds and the delete fails, `move` throws `move_left_a_copy` and **keeps the destination**, so you have two objects rather than none. The original error is on the error's cause.

---

## Incomplete multipart uploads

A multipart upload becomes an object only when it is completed. Until then its parts are billed storage that `list` cannot see, and the bucket cannot be deleted while one exists. A browser tab closed mid-upload leaves exactly this behind.

`put` and a browser cancel abort their own uploads on failure. Anything else needs a sweep. The cron that runs it is on [Abandoned uploads](/blob/uploads/abandoned-uploads#sweeping-incomplete-multipart-uploads); the calls it uses are below. Signatures: [TypeScript](/blob/sdks/ts/bucket#multipart-cleanup), [Python](/blob/sdks/py/bucket#multipart-cleanup), [Go](/blob/sdks/go/bucket#multipart-cleanup).

### Listing incomplete uploads

<CodeGroup>
```ts TypeScript
const uploads = await bucket.listMultipartUploads({ prefix: "uploads/" })
// [{ path: 'uploads/big.mp4', uploadId: 'ABC...', initiatedAt: Date }, ...]
```

```python Python
uploads = bucket.list_multipart_uploads(prefix="uploads/")
# [MultipartUpload(path='uploads/big.mp4', upload_id='ABC...', initiated_at=datetime(...)), ...]
```

```go Go
uploads, err := bucket.ListMultipartUploads(ctx, "uploads/")
// [{Path: "uploads/big.mp4", UploadID: "ABC...", InitiatedAt: time.Time}, ...]
```
</CodeGroup>

Returns every upload started and neither completed nor aborted, paging internally until it has them all. The prefix is optional.

| Field | Meaning |
| --- | --- |
| Path | The key the upload was started for. Nothing is stored there yet. |
| Upload id | Storage's id for the upload, needed to abort it. |
| Initiated at | When it was started. What "stale" is measured against. |

### Aborting one upload

<CodeGroup>
```ts TypeScript
await bucket.abortMultipartUpload({ path: "uploads/big.mp4", uploadId: "ABC..." })
```

```python Python
from upstash_blob import MultipartUpload

bucket.abort_multipart_upload(MultipartUpload(path="uploads/big.mp4", upload_id="ABC...", initiated_at=None))
```

```go Go
bucket.AbortMultipartUpload(ctx, blob.MultipartUpload{
	Path:     "uploads/big.mp4",
	UploadID: "ABC...",
})
```
</CodeGroup>

Throws the upload away with every part that landed for it. An upload that is already gone counts as success. An empty upload id is refused with `invalid_input`.

The completion callback receives the multipart upload id for exactly this pair. Store it with your row and you can abort a specific upload later without listing the bucket. It is not set for a single PUT.

### Aborting stale uploads

<CodeGroup>
```ts TypeScript
const aborted = await bucket.abortStaleMultipartUploads({
  olderThan: "1d",
  prefix: "uploads/",
})
// [{ path, uploadId, initiatedAt }, ...]
```

```python Python
aborted = bucket.abort_stale_multipart_uploads(older_than="1d", prefix="uploads/")
# [MultipartUpload(path, upload_id, initiated_at), ...]
```

```go Go
aborted, err := bucket.AbortStaleMultipartUploads(ctx, 24*time.Hour, "uploads/")
// [{Path, UploadID, InitiatedAt}, ...]
```
</CodeGroup>

List plus abort in one call, meant for a cron. The older-than window is required, a [Duration](/blob/reference/types#duration). In Go it is a `time.Duration`, and zero aborts every incomplete upload under the prefix. Only uploads started longer ago than that are touched, so a window longer than your slowest upload never aborts one still running. A day is a reasonable default.

<Note>
An abandoned upload **under** the multipart threshold is not a multipart upload. It is an ordinary stored object that `list` can see, and none of the calls above can find it. See [Abandoned uploads](/blob/uploads/abandoned-uploads).
</Note>

---

## Error codes

| Code | Raised by |
| --- | --- |
| `partial_delete` | A list or prefix delete where objects survived. The error lists them. |
| `move_left_a_copy` | `move`, when the source delete failed. |
| `invalid_input` | A bad delete target, an empty prefix without the all flag, an empty upload id. |

A delete never raises `not_found`. Statuses and extra fields are on [Errors](/blob/reference/errors#the-codes).
