---
title: @vercel/blob
product: vercel
url: /docs/vercel-blob/using-blob-sdk
canonical_url: "https://vercel.com/docs/vercel-blob/using-blob-sdk"
last_updated: 2026-08-26
type: reference
prerequisites:
  - /docs/vercel-blob
related:
  - /docs/cli/blob
  - /docs/cli/env
  - /docs/oidc
  - /docs/vercel-blob/vercel-signed-urls
  - /docs/vercel-blob
summary: Learn how to use the Vercel Blob SDK to access your blob store from your apps.
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

# @vercel/blob

> **🔒 Permissions Required**: Vercel Blob

## Getting started

To start using [Vercel Blob](/storage/blob) SDK, follow the steps below:


<!-- docsgraph:related -->
## Related pages

> **For AI agents:** Follow these links to understand how this page connects to the rest of the Vercel ecosystem. For the full cross-link map (inbound, outbound, prerequisites, and semantic neighbors), see the .graph.md link below.

- [Vercel Blob now supports OIDC authentication](https://vercel.com/changelog/vercel-blob-now-supports-oidc-authentication?from=related&source_path=%2Fdocs%2Fvercel-blob%2Fusing-blob-sdk&source_site=vercel-docs&relationship=related)
- [Migrate a Next.js app from Webflow Cloud to Vercel](https://vercel.com/kb/guide/migrate-a-next-js-app-from-webflow-cloud-to-vercel?from=related&source_path=%2Fdocs%2Fvercel-blob%2Fusing-blob-sdk&source_site=vercel-docs&relationship=related) — Move your Next.js app from Webflow Cloud to Vercel: remove the OpenNext Cloudflare adapter, drop the base path, map stor
- [Migrate a TanStack Start app from Cloudflare to Vercel](https://vercel.com/kb/guide/migrate-a-tanstack-start-app-from-cloudflare-to-vercel?from=related&source_path=%2Fdocs%2Fvercel-blob%2Fusing-blob-sdk&source_site=vercel-docs&relationship=related) — Move your TanStack Start app off Cloudflare Workers and onto Vercel Functions, where Fluid compute scales it automatical
- [Migrate a TanStack Start app from Netlify to Vercel](https://vercel.com/kb/guide/migrate-a-tanstack-start-app-from-netlify-to-vercel?from=related&source_path=%2Fdocs%2Fvercel-blob%2Fusing-blob-sdk&source_site=vercel-docs&relationship=related) — Move your TanStack Start app off Netlify and onto Vercel Functions, where Fluid compute scales it automatically. Swap to
- [Migrate an Astro app from Webflow Cloud to Vercel](https://vercel.com/kb/guide/migrate-an-astro-app-from-webflow-cloud-to-vercel?from=related&source_path=%2Fdocs%2Fvercel-blob%2Fusing-blob-sdk&source_site=vercel-docs&relationship=related) — Move your Astro app from Webflow Cloud to Vercel: swap the @astrojs/cloudflare adapter for @astrojs/vercel, drop the bas
- [The Complete Guide to Vercel Blob](https://vercel.com/kb/guide/vercel-blob?from=related&source_path=%2Fdocs%2Fvercel-blob%2Fusing-blob-sdk&source_site=vercel-docs&relationship=related) — Vercel Blob stores and serves files of any size through Vercel's global network. Learn how Blob works, what it costs, an
- [Public Storage](https://vercel.com/docs/vercel-blob/public-storage?from=related&source_path=%2Fdocs%2Fvercel-blob%2Fusing-blob-sdk&source_site=vercel-docs&relationship=related) — Learn how to use public Vercel Blob storage to serve files accessible to anyone with the URL
- [Complete a blob upload](https://vercel.com/docs/rest-api/vcr/complete-a-blob-upload?from=related&source_path=%2Fdocs%2Fvercel-blob%2Fusing-blob-sdk&source_site=vercel-docs&relationship=related) — PUT /v2/{teamSlug}/{projectSlug}/{repositoryName}/blobs/uploads/{uuid} — PUT /v2/:teamSlug/:projectSlug/:repositoryName/
- [Create a Blob store](https://vercel.com/docs/rest-api/storage/create-a-blob-store?from=related&source_path=%2Fdocs%2Fvercel-blob%2Fusing-blob-sdk&source_site=vercel-docs&relationship=related) — POST /storage/stores/blob — Create a Blob store

Full cross-link map for this page: [/docs/vercel-blob/using-blob-sdk.graph.md](/docs/vercel-blob/using-blob-sdk.graph.md?from=related&source_path=%2Fdocs%2Fvercel-blob%2Fusing-blob-sdk&source_site=vercel-docs&relationship=graph)
<!-- /docsgraph:related -->

> **💡 Note:** You can also interact with Vercel Blob using the [Vercel CLI](/docs/cli/blob)
> for command-line operations. For example, you might want to quickly upload
> assets during local development without writing additional code.

Vercel Blob works with any frontend framework. begin by installing the package:

**TypeScript**

<CodeBlock>
  <Code tab="pnpm">
    ```bash
    pnpm i @vercel/blob
    ```
  </Code>
  <Code tab="yarn">
    ```bash
    yarn i @vercel/blob
    ```
  </Code>
  <Code tab="npm">
    ```bash
    npm i @vercel/blob
    ```
  </Code>
  <Code tab="bun">
    ```bash
    bun i @vercel/blob
    ```
  </Code>
</CodeBlock>

**Python**

```bash
pip install vercel
```

- ### Create a Blob store
  1. Go to your project's [**Storage** tab](https://vercel.com/d?to=%2F%5Bteam%5D%2F%5Bproject%5D%2Fstores\&title=Go+to+Storage)
  2. Select **Create Database**, then choose **Blob**
  3. Select **Continue**, then set the access to **Private** or **Public**
  4. Choose a name for your store and select **Create a new Blob store**
  5. Select the environments where you would like the read-write token to be included. **Production** and **Preview** are preselected; include **Development** if you plan to work with the store locally. You can also update the prefix of the Environment Variable in Advanced Options
  Once created, you are taken to the Vercel Blob store page.

- ### Prepare your local project
  When you create the Blob store, Vercel adds one environment variable to the projects you selected:
  - `BLOB_READ_WRITE_TOKEN`: a long-lived static read-write token. Use it for code that runs outside Vercel or to generate client tokens for browser uploads.
  To use this environment variable locally, use the Vercel CLI to [pull the values into your local project](/docs/cli/env#exporting-development-environment-variables):
  ```bash
  vercel env pull
  ```

- ### Connect to a project (highly recommended)
  OpenID Connect (OIDC) is the default authentication on Vercel and is more secure than the long-lived `BLOB_READ_WRITE_TOKEN`. OIDC tokens rotate automatically, which removes the risk that a static secret leaks from your codebase or environment. Learn more about [how OIDC token federation works](/docs/oidc#how-oidc-token-federation-works).

  When you connect your Blob store to a project, Vercel adds three environment variables to that project:
  - `BLOB_STORE_ID`: the id of your Blob store. The SDK pairs this with `VERCEL_OIDC_TOKEN` to authenticate requests.
  - `VERCEL_OIDC_TOKEN`: a short-lived OIDC token that Vercel issues and rotates automatically. The SDK reads it from the environment and refreshes it when it expires, so you never handle it directly.
  - `BLOB_WEBHOOK_PUBLIC_KEY`: the public key the SDK uses to verify webhook callbacks signed by Vercel Blob when uploads are done via presigned URLs (or via `handleUploadPresigned`).
  To connect a project:
  1. Go to your Blob store's **Projects** tab
  2. Select **Connect to Project**
  3. Choose the project and the environments to connect. **Production** and **Preview** are preselected; include **Development** if you want to work with the store locally through `vercel env pull`
  You can change the connected environments at any time. From the store's **Projects** tab, open the context menu (⋯) next to your project, select **Update Project Connection**, and choose the environments. After saving, run `vercel env pull` again to refresh your local environment variables.

  See [Authentication](/docs/vercel-blob/using-blob-sdk#authentication) for the full credential resolution order.

## Authentication

Use OIDC when your code runs on Vercel. Use a static read-write token when your code runs outside Vercel or when you generate client tokens for browser uploads.

### OIDC tokens (recommended)

When your application runs on Vercel, the OIDC token is always available. Vercel issues a short-lived OpenID Connect token, exposes it as the `VERCEL_OIDC_TOKEN` environment variable, and rotates it automatically. The SDK reads this variable and pairs it with your store id to authenticate requests. You never need to read, supply, or refresh the token yourself. Because the token is short-lived and rotates automatically, no long-lived secret can leak from your codebase or environment.

To use OIDC, the following environment variables must be present:

- `VERCEL_OIDC_TOKEN`: managed by Vercel. On deployments, Vercel issues and rotates it automatically. For local development, run `vercel env pull` once to fetch it. Development tokens expire after 12 hours, and the SDK refreshes an expired token automatically using your Vercel CLI credentials, so you don't need to run `vercel env pull` again.
- `BLOB_STORE_ID`: the id of the store you want to read or write. Vercel creates this variable when you connect a store to your project. The SDK accepts the value in either `store_<id>` or `<id>` form.

When both are present, the SDK uses OIDC by default:

```ts
import { put } from '@vercel/blob';

// No token handling needed. The SDK reads VERCEL_OIDC_TOKEN and
// BLOB_STORE_ID from the environment and refreshes the token when
// it expires.
await put('media/photo.png', file, { access: 'private' });
```

> **⚠️ Warning:** Don't read `VERCEL_OIDC_TOKEN` yourself and pass it to SDK calls or store it
> somewhere else. A token you pass explicitly bypasses the SDK's automatic
> refresh: it works until the token expires, then every call fails with a
> `403` error. Let the SDK read the token from the environment.

#### Passing OIDC credentials explicitly

Some frameworks do not populate `process.env` from `.env.local` automatically. Vite, for example, only exposes variables prefixed with `VITE_` to client code, and server code requires a plugin like `dotenv-expand` or a custom config to load `.env.local` into `process.env`. In those environments, the SDK cannot read `VERCEL_OIDC_TOKEN` from the environment, so OIDC auto-configuration silently falls back to `BLOB_READ_WRITE_TOKEN` (or throws if no read-write token is available either).

You have two options:

- Configure your framework to load `.env.local` into `process.env` (for example, with `dotenv-expand`).
- Pass the OIDC credentials directly on each call using the `oidcToken` and `storeId` options:

  ```ts
  import { put } from '@vercel/blob';

  await put('media/photo.png', file, {
    access: 'private',
    oidcToken: loadOidcToken(), // your own loader
    storeId: loadStoreId(),
  });
  ```

The `oidcToken` option mirrors `token` for read-write credentials, so OIDC credentials no longer have to come from the environment.

> **⚠️ Warning:** A token passed through `oidcToken` is not refreshed automatically. Your code
> becomes responsible for supplying a valid, unexpired token on every call, and
> requests fail with a `403` error once the token expires. Prefer configuring
> your framework to load `.env.local` into `process.env`, and use this option
> only when that is not possible.

> **💡 Note:** `handleUpload` always requires a read-write token to sign client tokens for
> browser uploads. OIDC is not accepted for this method. See [Read-write
> tokens](#read-write-tokens). To run client uploads with OIDC, use
> [`handleUploadPresigned`](#handleuploadpresigned) instead: it hands out
> presigned upload URLs through [Vercel Signed
> URLs](/docs/vercel-blob/vercel-signed-urls), a flow that works with either
> credential.

### Read-write tokens

A read-write token is a long-lived static credential. Use one when your code runs outside Vercel, for example in a CI job or on another host.

When you create a Blob store from the Vercel dashboard, an environment variable named `BLOB_READ_WRITE_TOKEN` is added to the projects you select. OIDC takes precedence when its environment variables are present; otherwise the SDK falls back to `BLOB_READ_WRITE_TOKEN`.

### Resolution order

The SDK resolves credentials in this order, stopping at the first match:

1. An explicit `token` option (a read-write token, or a client token created with `generateClientTokenFromReadWriteToken`). This always wins, including over OIDC.
2. OIDC credentials, paired with a store id:

   - The OIDC token comes from the `oidcToken` option if set, otherwise from `process.env.VERCEL_OIDC_TOKEN`.
   - The store id comes from the `storeId` option if set, otherwise from `process.env.BLOB_STORE_ID`.

   Both an OIDC token and a store id must be available for this tier to match. The SDK accepts the store id in either `store_<id>` or `<id>` form.
3. `process.env.BLOB_READ_WRITE_TOKEN`.
4. If none of the above is available, the SDK throws an error.

## The `access` parameter

While the store itself determines whether files are [private or public](/docs/vercel-blob#private-and-public-storage), most SDK methods require you to pass `access: 'private'` or `access: 'public'`. This makes it explicit in your code what kind of data access you're dealing with, so anyone reading the code immediately understands the security context.

## Using the SDK methods

In the examples below, we use [Fluid compute](/docs/fluid-compute) for optimal performance and scalability.

## Upload a blob

This example creates a Function that accepts a file from a `multipart/form-data` form and uploads it to the Blob store. The function returns a unique URL for the blob.

**TypeScript**

```ts filename="app/upload/route.ts" framework=nextjs-app
import { put } from '@vercel/blob';

export async function PUT(request: Request) {
  const form = await request.formData();
  const file = form.get('file') as File;
  const blob = await put(file.name, file, {
    access: 'private' /* or 'public' */,
    addRandomSuffix: true,
  });

  return Response.json(blob);
}
```

```js filename="app/upload/route.js" framework=nextjs-app
import { put } from '@vercel/blob';

export async function PUT(request) {
  const form = await request.formData();
  const file = form.get('file');
  const blob = await put(file.name, file, {
    access: 'private' /* or 'public' */,
    addRandomSuffix: true,
  });

  return Response.json(blob);
}
```

```ts filename="app/upload/route.ts" framework=nextjs
import { put } from '@vercel/blob';

export async function PUT(request: Request) {
  const form = await request.formData();
  const file = form.get('file') as File;
  const blob = await put(file.name, file, {
    access: 'private' /* or 'public' */,
    addRandomSuffix: true,
  });

  return Response.json(blob);
}
```

```js filename="app/upload/route.js" framework=nextjs
import { put } from '@vercel/blob';

export async function PUT(request) {
  const form = await request.formData();
  const file = form.get('file');
  const blob = await put(file.name, file, {
    access: 'private' /* or 'public' */,
    addRandomSuffix: true,
  });

  return Response.json(blob);
}
```

```ts filename="api/upload.ts" framework=other
import { put } from '@vercel/blob';

export async function PUT(request: Request) {
  const form = await request.formData();
  const file = form.get('file') as File;
  const blob = await put(file.name, file, {
    access: 'private' /* or 'public' */,
    addRandomSuffix: true,
  });

  return Response.json(blob);
}
```

```js filename="api/upload.js" framework=other
import { put } from '@vercel/blob';

export async function PUT(request) {
  const form = await request.formData();
  const file = form.get('file');
  const blob = await put(file.name, file, {
    access: 'private' /* or 'public' */,
    addRandomSuffix: true,
  });

  return Response.json(blob);
}
```

**Python**

```python
import asyncio
import os
import tempfile
from dotenv import load_dotenv
from vercel.blob import UploadProgressEvent, BlobClient, AsyncBlobClient

load_dotenv(".env.local")
load_dotenv()

def on_progress(e: UploadProgressEvent) -> None:
    print(f"progress: {e.loaded}/{e.total} bytes ({e.percentage}%)")

async def handler(form: dict) -> dict:
    client = AsyncBlobClient()

    file: bytes = form["file"]  # your uploaded bytes
    uploaded = await client.put(
        f"profiles/{form['filename']}",
        file,
        access="private",  # or "public",
        add_random_suffix=True,
        on_upload_progress=on_progress,
    )
    return dict(uploaded)
```

### `put()`

The `put` method uploads a blob object to the Blob store.

**TypeScript**

```js
put(pathname, body, options);
```

**Python**

```python
put(
    pathname: str,
    body: bytes | AsyncIterator[bytes],
    *,
    access: Literal['private', 'public'],
    content_type: str | None = None,
    add_random_suffix: bool = False,
    overwrite: bool = False,
    cache_control_max_age: int | None = None,
    token: str | None = None,
    multipart: bool | None = None,
    on_upload_progress: Callable[[UploadProgressEvent], None] | None = None
)
```

It accepts the following parameters:

- `pathname`: (Required) A string specifying the base value of the return URL
- `body`: (Required) A blob object as `ReadableStream`, `String`, `ArrayBuffer` or `Blob` based on these [supported body types](https://developer.mozilla.org/docs/Web/API/fetch#body)
- `options`: (Required) A `JSON` object with the following required and optional parameters:

| Parameter            | Required | Values                                                                                                                                                                                                                                                                                        |
| -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `access`             | Yes      | [`'private'` or `'public'`](/docs/vercel-blob#private-and-public-storage). Determines the access level of the blob.                                                                                                                                                                           |
| `addRandomSuffix`    | No       | A boolean specifying whether to add a random suffix to the `pathname`. It defaults to `false`. **We recommend using this option** to ensure there are no conflicts in your blob filenames.                                                                                                    |
| `allowOverwrite`     | No       | A boolean to allow overwriting blobs. By default an error will be thrown if you try to overwrite a blob by using the same `pathname` for multiple blobs.                                                                                                                                      |
| `cacheControlMaxAge` | No       | A number in seconds to configure how long Blobs are cached. Defaults to one month. Cannot be set to a value lower than 1 minute. See the [caching](/docs/vercel-blob#caching) documentation for more details.                                                                        |
| `contentType`        | No       | A string indicating the [media type](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type). By default, it's extracted from the pathname's extension.                                                                                                                             |
| `token`              | No       | A static read-write token. Defaults to `process.env.BLOB_READ_WRITE_TOKEN`. Its default value is not used when OIDC credentials are present, but an explicitly passed token always takes priority. You can also pass a client token created with `generateClientTokenFromReadWriteToken`. See [Authentication](#authentication). |
| `oidcToken`          | No       | A Vercel OIDC token, used in place of `process.env.VERCEL_OIDC_TOKEN`. Pair with `storeId` (or `BLOB_STORE_ID`). Useful when your framework does not load `.env.local` into `process.env` automatically. An explicitly passed token is not refreshed automatically. See [Authentication](#authentication). |
| `storeId`            | No       | The Blob store id, used with OIDC. Defaults to `process.env.BLOB_STORE_ID`. The SDK accepts either `store_<id>` or `<id>` form. See [Authentication](#authentication). |
| `multipart`          | No       | Pass `multipart: true` when uploading large files. It will split the file into multiple parts, upload them in parallel and retry failed parts. |
| `abortSignal`        | No       | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the operation                                                                                                                                                                                        |
| `onUploadProgress`   | No       | Callback to track upload progress: `onUploadProgress({loaded: number, total: number, percentage: number})`                                                                                                                                                                                    |
| `ifMatch`            | No       | An ETag value. The operation only succeeds if the blob's current ETag matches this value. Use this for [conditional writes](/docs/vercel-blob#conditional-writes) to prevent overwriting changes made by others. Throws `BlobPreconditionFailedError` if the ETag doesn't match.              |

#### Example code with folder output

To upload your file to an existing [folder](#folders) inside your blob storage, pass the folder name in the `pathname` as shown below:

**TypeScript**

```ts filename="app/upload/route.ts"
const imageFile = formData.get('image') as File;
const blob = await put(`existingBlobFolder/${imageFile.name}`, imageFile, {
  access: 'private' /* or 'public' */,
  addRandomSuffix: true,
});
```

**Python**

```python
import os
from dotenv import load_dotenv
from vercel.blob import AsyncBlobClient

load_dotenv('.env.local')
load_dotenv()

client = AsyncBlobClient()

image_bytes = b"..."
blob = await client.put(
    f"existingBlobFolder/image.png",
    image_bytes,
    access="private",  # or "public",
    add_random_suffix=True,
)
```

#### Example responses

`put()` returns a `JSON` object with the following data for the created blob object:

```json
{
  "pathname": "string",
  "contentType": "string",
  "contentDisposition": "string",
  "url": "string",
  "downloadUrl": "string",
  "etag": "string"
}
```

An example blob (uploaded with `addRandomSuffix: true`) is:

```json
{
  "pathname": "profilesv1/user-12345-NoOVGDVcqSPc7VYCUAGnTzLTG2qEM2.txt",
  "contentType": "text/plain",
  "contentDisposition": "attachment; filename=\"user-12345-NoOVGDVcqSPc7VYCUAGnTzLTG2qEM2.txt\"",
  "url": "https://ce0rcu23vrrdzqap.private.blob.vercel-storage.com/profilesv1/user-12345-NoOVGDVcqSPc7VYCUAGnTzLTG2qEM2.txt",
  "downloadUrl": "https://ce0rcu23vrrdzqap.private.blob.vercel-storage.com/profilesv1/user-12345-NoOVGDVcqSPc7VYCUAGnTzLTG2qEM2.txt?download=1",
  "etag": "\"a1b2c3d4e5f6\""
}
```

> **💡 Note:** If `access` is `'public'`, the URL domain will be `.public.blob.vercel-storage.com` instead of `.private.blob.vercel-storage.com`.

An example blob uploaded without `addRandomSuffix: true` (default) is:

```json
{
  "pathname": "profilesv1/user-12345.txt",
  "contentType": "text/plain",
  "contentDisposition": "attachment; filename=\"user-12345.txt\"",
  // no automatic random suffix added 👇
  "url": "https://ce0rcu23vrrdzqap.private.blob.vercel-storage.com/profilesv1/user-12345.txt",
  "downloadUrl": "https://ce0rcu23vrrdzqap.private.blob.vercel-storage.com/profilesv1/user-12345.txt?download=1",
  "etag": "\"f6e5d4c3b2a1\""
}
```

## Upload an optimized image

Store images as optimized versions instead of originals. The image is [optimized](/docs/image-optimization) once at write time, and only the optimized output lands in your Blob store. You then serve the stored file like any other blob, meaning you pay for [Blob Data Transfer](/docs/vercel-blob#blob-data-transfer) instead of [Fast Data Transfer](/docs/manage-cdn-usage#fast-data-transfer).

### `putImage()`

The `putImage` method allows you to change the width, quality, and/or format of an image and then uploads the result to the Blob store.

```js
putImage(pathname, bodyOrUrl, options);
```

It accepts the following parameters:

- `pathname`: (Required) A string specifying the base value of the return URL
- `bodyOrUrl`: (Required) The image content as `ReadableStream`, `String`, `ArrayBuffer` or `Blob` based on these [supported body types](https://developer.mozilla.org/docs/Web/API/fetch#body), or a `URL` instance pointing to a public `http(s)` image. When you pass a `URL` instance, Vercel fetches the image server-side, so you don't need to download it first. Strings are always treated as image content, even when they look like URLs.
- `options`: (Required) A `JSON` object with the following required and optional parameters:

| Parameter            | Required | Values                                                                                                                                                                                                                                                                                        |
| -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `access`             | Yes      | [`'private'` or `'public'`](/docs/vercel-blob#private-and-public-storage). Determines the access level of the blob.                                                                                                                                                                           |
| `optimizeImage`      | Yes      | An object describing the transformation to apply. See [the `optimizeImage` parameter](#the-optimizeimage-parameter) below.                                                                                                                                                                    |
| `addRandomSuffix`    | No       | A boolean specifying whether to add a random suffix to the `pathname`. It defaults to `false`. **We recommend using this option** to ensure there are no conflicts in your blob filenames.                                                                                                    |
| `allowOverwrite`     | No       | A boolean to allow overwriting blobs. By default an error will be thrown if you try to overwrite a blob by using the same `pathname` for multiple blobs.                                                                                                                                      |
| `cacheControlMaxAge` | No       | A number in seconds to configure how long Blobs are cached. Defaults to one month. Cannot be set to a value lower than 1 minute. See the [caching](/docs/vercel-blob#caching) documentation for more details.                                                                                 |
| `ifMatch`            | No       | An ETag value. The operation only succeeds if the blob's current ETag matches this value. Use this for [conditional writes](/docs/vercel-blob#conditional-writes) to prevent overwriting changes made by others. Throws `BlobPreconditionFailedError` if the ETag doesn't match.              |
| `oidcToken`          | No       | A Vercel OIDC token, used in place of `process.env.VERCEL_OIDC_TOKEN`. Pair with `storeId` (or `BLOB_STORE_ID`). Useful when your framework does not load `.env.local` into `process.env` automatically. An explicitly passed token is not refreshed automatically. See [Authentication](#authentication). |
| `storeId`            | No       | The Blob store id, used with OIDC. Defaults to `process.env.BLOB_STORE_ID`. The SDK accepts either `store_<id>` or `<id>` form. See [Authentication](#authentication). |
| `abortSignal`        | No       | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the operation                                                                                                                                                                                        |
| `onUploadProgress`   | No       | Callback to track upload progress: `onUploadProgress({loaded: number, total: number, percentage: number})`                                                                                                                                                                                    |

Two options from `put()` are not available on `putImage()`:

- `contentType`: the stored content type always comes from the optimizer output.
- `multipart`: optimized uploads cannot be split into parts.

> **💡 Note:** `putImage()` requires [OIDC credentials](#oidc-tokens-recommended). Read-write
> tokens are not accepted for optimized uploads.

#### The `optimizeImage` parameter

The `optimizeImage` object controls the transformation:

| Parameter | Required | Values                                                                                                                          |
| --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `width`   | Yes      | The width of the optimized image in pixels, an integer between 1 and 8192. The aspect ratio of the source image is preserved.   |
| `quality` | No       | The quality of the optimized image, an integer between 1 (lowest quality) and 100 (highest quality). It defaults to 75.         |
| `format`  | No       | The output format: `'jpeg'`, `'png'`, `'webp'`, or `'avif'`. The source image format is preserved when omitted.                 |

If the optimized output would be larger than the source image, the source image is stored unchanged. Check the `contentType` in the response to confirm a format conversion happened.

#### Example

This example stores a 256 pixel wide WebP version of an uploaded avatar, and a 1200 pixel wide AVIF cover image fetched from a public URL:

```ts filename="app/api/avatar/route.ts"
import { putImage } from '@vercel/blob';

export async function POST(request: Request) {
  const blob = await putImage('avatars/user-123.webp', request.body, {
    access: 'public',
    optimizeImage: { width: 256, quality: 80, format: 'webp' },
  });

  const cover = await putImage(
    'covers/launch.avif',
    new URL('https://example.com/original-photo.jpg'),
    {
      access: 'public',
      optimizeImage: { width: 1200, format: 'avif' },
    },
  );

  return Response.json({ avatarUrl: blob.url, coverUrl: cover.url });
}
```

#### Example response

`putImage()` returns the same `JSON` object as [`put()`](#put):

```json
{
  "pathname": "avatars/user-123.webp",
  "contentType": "image/webp",
  "contentDisposition": "attachment; filename=\"user-123.webp\"",
  "url": "https://ce0rcu23vrrdzqap.public.blob.vercel-storage.com/avatars/user-123.webp",
  "downloadUrl": "https://ce0rcu23vrrdzqap.public.blob.vercel-storage.com/avatars/user-123.webp?download=1",
  "etag": "\"a1b2c3d4e5f6\""
}
```

#### Pricing

Each `putImage()` call is billed as one [image transformation](/docs/image-optimization/limits-and-pricing#image-transformations) plus a regular blob upload at standard [Vercel Blob pricing](/docs/vercel-blob/usage-and-pricing). Reads of the stored blob are regular blob reads: they don't use Image Optimization and don't incur transformation charges.

> **💡 Note:** Earlier SDK versions exposed an `optimizeImage` option on `put()` and a
> `putFromUrl()` method. Both are deprecated in favor of `putImage()` and keep
> working.

You can also optimize and store images from the terminal with [`vercel blob put-image`](/docs/cli/blob#put-image).

## Get a blob

Retrieve blob content as a stream. For private blobs, this is how you deliver files through your functions. For public blobs, you can use this to process blob content server-side.

### `get()`

**TypeScript**

```js
get(urlOrPathname, options);
```

**Python**

```python
get(
    url_or_path: str,
    *,
    access: Literal['private', 'public'],
    token: str | None = None,
    if_none_match: str | None = None,
) -> GetBlobResult | None
```

It accepts the following parameters:

- `urlOrPathname`: (Required) A string specifying the URL or pathname of the blob object to retrieve
- `options`: (Required) A `JSON` object with the following required and optional parameters:

| Parameter     | Required | Values                                                                                                                                                                                                                                                                            |
| ------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `access`      | Yes      | [`'private'` or `'public'`](/docs/vercel-blob#private-and-public-storage). Determines the access level of the blob.                                                                                                                                                               |
| `token`       | No       | A static read-write token. Defaults to `process.env.BLOB_READ_WRITE_TOKEN`. Its default value is not used when OIDC credentials are present, but an explicitly passed token always takes priority. See [Authentication](#authentication). |
| `oidcToken`   | No       | A Vercel OIDC token, used in place of `process.env.VERCEL_OIDC_TOKEN`. Pair with `storeId` (or `BLOB_STORE_ID`). Useful when your framework does not load `.env.local` into `process.env` automatically. An explicitly passed token is not refreshed automatically. See [Authentication](#authentication). |
| `storeId`     | No       | The Blob store id, used with OIDC. Defaults to `process.env.BLOB_STORE_ID`. The SDK accepts either `store_<id>` or `<id>` form. See [Authentication](#authentication). |
| `ifNoneMatch` | No       | An ETag value. When the blob's current ETag matches, returns `statusCode: 304` with `stream: null` instead of the full response. See [browser caching with conditional requests](/docs/vercel-blob/private-storage#browser-caching-with-conditional-requests) for a full example. |
| `useCache`    | No       | Set to `false` to guarantee the read returns the latest version of the blob, at the cost of slower reads. Defaults to `true`. See [Consistent reads](/docs/vercel-blob/private-storage#consistent-reads).                                                     |
| `headers`     | No       | Additional headers to include in the fetch request. The authorization header is set automatically.                                                                                                                                                                                |
| `abortSignal` | No       | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the operation                                                                                                                                                                            |

`get()` returns `null` (`None` in Python) if the blob is not found, or an object with the following properties:

**TypeScript**

```ts
{
  statusCode: number; // 200 or 304
  stream: ReadableStream<Uint8Array> | null; // null on 304
  headers: Headers;
  blob: {
    url: string;
    downloadUrl: string;
    pathname: string;
    contentType: string | null; // null on 304
    contentDisposition: string;
    cacheControl: string;
    etag: string;
    size: number | null; // null on 304
    uploadedAt: Date;
  };
}
```

**Python**

```python
# GetBlobResult:
result.status_code          # int (200 or 304)
result.stream               # AsyncIterator[bytes] | None (None on 304)
result.headers              # dict
result.blob.url             # str
result.blob.download_url    # str
result.blob.pathname        # str
result.blob.content_type    # str | None (None on 304)
result.blob.content_disposition  # str
result.blob.cache_control   # str
result.blob.etag            # str
result.blob.size            # int | None (None on 304)
result.blob.uploaded_at     # datetime
```

#### Example

**TypeScript**

```ts filename="app/api/documents/[...pathname]/route.ts"
import { type NextRequest, NextResponse } from 'next/server';
import { get } from '@vercel/blob';

export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ pathname: string[] }> },
) {
  // Your auth goes here: await authRequest(request)

  const { pathname } = await params;
  const result = await get(pathname.join('/'), { access: 'private' });

  if (result?.statusCode !== 200) {
    return new NextResponse('Not found', { status: 404 });
  }

  return new NextResponse(result.stream, {
    headers: {
      'Content-Type': result.blob.contentType,
    },
  });
}
```

**Python**

```python
from vercel.blob import AsyncBlobClient

client = AsyncBlobClient()

result = await client.get("documents/report.pdf", access="private")

if result is None or result.status_code != 200:
    print("Not found")
else:
    # result.stream is an async iterator of bytes
    async for chunk in result.stream:
        # process each chunk
        pass
```

#### Example response

`get()` returns `null` (`None` in Python) if the blob is not found, or an object with the following properties:

**TypeScript**

```ts
{
  statusCode: number; // 200 or 304
  stream: ReadableStream<Uint8Array> | null; // null on 304
  headers: Headers;
  blob: {
    url: string;
    downloadUrl: string;
    pathname: string;
    contentType: string | null; // null on 304
    contentDisposition: string;
    cacheControl: string;
    etag: string;
    size: number | null; // null on 304
    uploadedAt: Date;
  };
}
```

**Python**

```python
# GetBlobResult:
result.status_code          # int (200 or 304)
result.stream               # AsyncIterator[bytes] | None (None on 304)
result.headers              # dict
result.blob.url             # str
result.blob.download_url    # str
result.blob.pathname        # str
result.blob.content_type    # str | None (None on 304)
result.blob.content_disposition  # str
result.blob.cache_control   # str
result.blob.etag            # str
result.blob.size            # int | None (None on 304)
result.blob.uploaded_at     # datetime
```

## Deleting blobs

This example creates a function that deletes a blob object from the Blob store. You can delete multiple blob objects in a single request by passing an array of blob URLs.

```ts filename="app/delete/route.ts" framework=nextjs-app
import { del } from '@vercel/blob';

export async function DELETE(request: Request) {
  const { searchParams } = new URL(request.url);
  const urlToDelete = searchParams.get('url') as string;
  await del(urlToDelete);

  return new Response();
}
```

```js filename="app/delete/route.js" framework=nextjs-app
import { del } from '@vercel/blob';

export async function DELETE(request) {
  const { searchParams } = new URL(request.url);
  const urlToDelete = searchParams.get('url');
  await del(urlToDelete);

  return new Response();
}
```

```ts filename="app/delete/route.ts" framework=nextjs
import { del } from '@vercel/blob';

export async function DELETE(request: Request) {
  const { searchParams } = new URL(request.url);
  const urlToDelete = searchParams.get('url') as string;
  await del(urlToDelete);

  return new Response();
}
```

```js filename="app/delete/route.js" framework=nextjs
import { del } from '@vercel/blob';

export async function DELETE(request) {
  const { searchParams } = new URL(request.url);
  const urlToDelete = searchParams.get('url');
  await del(urlToDelete);

  return new Response();
}
```

```ts filename="api/blob.ts" framework=other
import { del } from '@vercel/blob';

export async function DELETE(request: Request) {
  const { searchParams } = new URL(request.url);
  const urlToDelete = searchParams.get('url') as string;
  await del(urlToDelete);

  return new Response();
}
```

```js filename="api/blob.js" framework=other
import { del } from '@vercel/blob';

export async function DELETE(request) {
  const { searchParams } = new URL(request.url);
  const urlToDelete = searchParams.get('url');
  await del(urlToDelete);

  return new Response();
}
```

### `del()`

The `del` method deletes one or multiple blob objects from the Blob store.

Since blobs are cached, it may take up to one minute for them to be fully removed from the Vercel CDN cache.

**TypeScript**

```ts
del(urlOrPathname, options);

del([urlOrPathname], options); // You can pass an array to delete multiple blob objects
```

**Python**

```python
delete(url_or_path: str | Iterable[str], *, token: str | None = None) -> None
```

It accepts the following parameters:

- `urlOrPathname`: (Required) A string or array of strings specifying the URL(s) or pathname(s) of the blob object(s) to delete.
- `options`: (Optional) A `JSON` object with the following optional parameter:

| Parameter     | Required | Values                                                                                                                                                                                                                                                                                                                    |
| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`       | No       | A static read-write token. Defaults to `process.env.BLOB_READ_WRITE_TOKEN`. Its default value is not used when OIDC credentials are present, but an explicitly passed token always takes priority. See [Authentication](#authentication). |
| `oidcToken`   | No       | A Vercel OIDC token, used in place of `process.env.VERCEL_OIDC_TOKEN`. Pair with `storeId` (or `BLOB_STORE_ID`). Useful when your framework does not load `.env.local` into `process.env` automatically. An explicitly passed token is not refreshed automatically. See [Authentication](#authentication). |
| `storeId`     | No       | The Blob store id, used with OIDC. Defaults to `process.env.BLOB_STORE_ID`. The SDK accepts either `store_<id>` or `<id>` form. See [Authentication](#authentication). |
| `ifMatch`     | No       | An ETag value. The delete only succeeds if the blob's current ETag matches this value. Use this for [conditional writes](/docs/vercel-blob#conditional-writes) to ensure you're deleting the expected version. Throws `BlobPreconditionFailedError` if the ETag doesn't match. Only works with a single URL (not arrays). |
| `abortSignal` | No       | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the operation                                                                                                                                                                                                                    |

`del()` returns a `void` response. A delete action is always successful if the blob url exists. A delete action won't throw if the blob url doesn't exist.

## Get blob metadata

This example creates a Function that returns a blob object's metadata.

```ts filename="app/get-blob/route.ts" framework=nextjs-app
import { head } from '@vercel/blob';

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const blobUrl = searchParams.get('url');
  const blobDetails = await head(blobUrl);

  return Response.json(blobDetails);
}
```

```js filename="app/get-blob/route.js" framework=nextjs-app
import { head } from '@vercel/blob';

export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const blobUrl = searchParams.get('url');
  const blobDetails = await head(blobUrl);

  return Response.json(blobDetails);
}
```

```ts filename="app/get-blob/route.ts" framework=nextjs
import { head } from '@vercel/blob';

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const blobUrl = searchParams.get('url');
  const blobDetails = await head(blobUrl);

  return Response.json(blobDetails);
}
```

```js filename="app/get-blob/route.js" framework=nextjs
import { head } from '@vercel/blob';

export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const blobUrl = searchParams.get('url');
  const blobDetails = await head(blobUrl);

  return Response.json(blobDetails);
}
```

```ts filename="/api/blob.ts" framework=other
import { head } from '@vercel/blob';

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const blobUrl = searchParams.get('url');
  const blobDetails = await head(blobUrl);

  return Response.json(blobDetails);
}
```

```js filename="/api/blob.js" framework=other
import { head } from '@vercel/blob';

export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const blobUrl = searchParams.get('url');
  const blobDetails = await head(blobUrl);

  return Response.json(blobDetails);
}
```

### `head()`

The `head` method returns a blob object's metadata.

**TypeScript**

```js
head(urlOrPathname, options);
```

**Python**

```python
head(url_or_path: str, *, token: str | None = None) -> HeadBlobResult
```

It accepts the following parameters:

- `urlOrPathname`: (Required) A string specifying the URL or pathname of the blob object to read.
- `options`: (Optional) A `JSON` object with the following optional parameter:

| Parameter     | Required | Values                                                                                                                                                                                                 |
| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `token`       | No       | A static read-write token. Defaults to `process.env.BLOB_READ_WRITE_TOKEN`. Its default value is not used when OIDC credentials are present, but an explicitly passed token always takes priority. See [Authentication](#authentication). |
| `oidcToken`   | No       | A Vercel OIDC token, used in place of `process.env.VERCEL_OIDC_TOKEN`. Pair with `storeId` (or `BLOB_STORE_ID`). Useful when your framework does not load `.env.local` into `process.env` automatically. An explicitly passed token is not refreshed automatically. See [Authentication](#authentication). |
| `storeId`     | No       | The Blob store id, used with OIDC. Defaults to `process.env.BLOB_STORE_ID`. The SDK accepts either `store_<id>` or `<id>` form. See [Authentication](#authentication). |
| `abortSignal` | No       | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the operation |

`head()` returns one of the following:

- a `JSON` object with the requested blob object's metadata
- throws a `BlobNotFoundError` if the blob object was not found

**TypeScript**

```ts
{
  size: number;
  uploadedAt: Date;
  pathname: string;
  contentType: string;
  contentDisposition: string;
  url: string;
  downloadUrl: string;
  cacheControl: string;
  etag: string;
}
```

**Python**

```python
result.size                 # int
result.uploaded_at          # datetime
result.pathname             # str
result.content_type         # str
result.content_disposition  # str
result.url                  # str
result.download_url         # str
result.cache_control        # str
result.etag                 # str
```

## List blobs

This example creates a Function that returns a list of blob objects in a Blob store.

```ts filename="app/get-blobs/route.ts" framework=nextjs-app
import { list } from '@vercel/blob';

export async function GET(request: Request) {
  const { blobs } = await list();
  return Response.json(blobs);
}
```

```js filename="app/get-blobs/route.js" framework=nextjs-app
import { list } from '@vercel/blob';

export async function GET(request) {
  const { blobs } = await list();
  return Response.json(blobs);
}
```

```ts filename="app/get-blobs/route.ts" framework=nextjs
import { list } from '@vercel/blob';

export async function GET(request: Request) {
  const { blobs } = await list();
  return Response.json(blobs);
}
```

```js filename="app/get-blobs/route.js" framework=nextjs
import { list } from '@vercel/blob';

export async function GET(request) {
  const { blobs } = await list();
  return Response.json(blobs);
}
```

```ts filename="api/blob.ts" framework=other
import { list } from '@vercel/blob';

export async function GET(request: Request) {
  const { blobs } = await list();
  return Response.json(blobs);
}
```

```js filename="api/blob.js" framework=other
import { list } from '@vercel/blob';

export async function GET(request) {
  const { blobs } = await list();
  return Response.json(blobs);
}
```

### `list()`

The `list` method returns a list of blob objects in a Blob store.

**TypeScript**

```js
list(options);
```

**Python**

```python
list_objects(
    *,
    limit: int | None = None,
    prefix: str | None = None,
    cursor: str | None = None,
    mode: str | None = None,
    token: str | None = None,
) -> ListBlobResult
```

It accepts the following parameters:

- `options`: (Optional) A `JSON` object with the following optional parameters:

| Parameter     | Required | Values                                                                                                                                                                                                 |
| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `token`       | No       | A static read-write token. Defaults to `process.env.BLOB_READ_WRITE_TOKEN`. Its default value is not used when OIDC credentials are present, but an explicitly passed token always takes priority. See [Authentication](#authentication). |
| `oidcToken`   | No       | A Vercel OIDC token, used in place of `process.env.VERCEL_OIDC_TOKEN`. Pair with `storeId` (or `BLOB_STORE_ID`). Useful when your framework does not load `.env.local` into `process.env` automatically. An explicitly passed token is not refreshed automatically. See [Authentication](#authentication). |
| `storeId`     | No       | The Blob store id, used with OIDC. Defaults to `process.env.BLOB_STORE_ID`. The SDK accepts either `store_<id>` or `<id>` form. See [Authentication](#authentication). |
| `limit`       | No       | A number specifying the maximum number of blob objects to return. It defaults to 1000 |
| `prefix`      | No       | A string used to filter for blob objects contained in a specific folder assuming that the folder name was used in the `pathname` when the blob object was uploaded                                     |
| `cursor`      | No       | A string obtained from a previous `list` response to be used for reading the next page of results                                                                                                      |
| `mode`        | No       | A string specifying the response format. Can either be `expanded` (default) or `folded`. In `folded` mode all blobs that are located inside a folder will be folded into a single folder string entry  |
| `abortSignal` | No       | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the operation                                                                                                 |

`list()` returns a `JSON` object in the following format:

**TypeScript**

```ts
{
  blobs: {
    size: number;
    uploadedAt: Date;
    pathname: string;
    url: string;
    downloadUrl: string;
    etag: string;
  }[];
  cursor?: string;
  hasMore: boolean;
  folders?: string[];
}
```

**Python**

```python
# ListBlobResult:
result.blobs          # list[ListBlobItem]
result.cursor         # str | None
result.has_more       # bool
result.folders        # list[str] | None

# ListBlobItem:
item.size # int
item.uploaded_at # datetime
item.pathname # str
item.url # str
item.download_url # str
item.etag # str
```

### Pagination

For a long list of blob objects (the default list `limit` is 1000), you can use the `cursor` and `hasMore` parameters to paginate through the results as shown in the example below:

**TypeScript**

```js
let hasMore = true;
let cursor;

while (hasMore) {
  const listResult = await list({
    cursor,
  });

  hasMore = listResult.hasMore;
  cursor = listResult.cursor;
}
```

**Python**

```python
from vercel.blob import list_objects

has_more = True
cursor = None

while has_more:
    page = list_objects(cursor=cursor, limit=1000)
    for b in page.blobs:
        # process each blob
        pass
    has_more = page.has_more
    cursor = page.cursor
```

### Folders

To retrieve the folders from your blob store, alter the `mode` parameter to modify the response format of the list operation.
The default value of `mode` is `expanded`, which returns all blobs in a single array of objects.

Alternatively, you can set `mode` to `folded` to roll up all blobs located inside a folder into a single entry.
These entries will be included in the response as `folders`. Blobs that are not located in a folder will still be returned in the blobs property.

By using the `folded` mode, you can efficiently retrieve folders and subsequently list the blobs inside them by using the returned `folders` as a `prefix` for further requests.
Omitting the `prefix` parameter entirely, will return all folders in the root of your store. Be aware that the blobs pathnames and the folder names will always be fully quantified and never relative to the prefix you passed.

**TypeScript**

```js
const {
  folders: [firstFolder],
  blobs: rootBlobs,
} = await list({ mode: 'folded' });

const { folders, blobs } = await list({ mode: 'folded', prefix: firstFolder });
```

**Python**

```python
from vercel.blob import list_objects

root = list_objects(mode="folded")
first_folder = root.folders[0]
sub = list_objects(mode="folded", prefix=first_folder)

```

## Copy a blob

This example creates a Function that copies an existing blob to a new path in the store.

```ts filename="app/copy-blob/route.ts" framework=nextjs-app
import { copy } from '@vercel/blob';

export async function PUT(request: Request) {
  const form = await request.formData();

  const fromUrl = form.get('fromUrl') as string;
  const toPathname = form.get('toPathname') as string;

  const blob = await copy(fromUrl, toPathname, { access: 'private' /* or 'public' */ });

  return Response.json(blob);
}
```

```js filename="app/copy-blob/route.js" framework=nextjs-app
import { copy } from '@vercel/blob';

export async function PUT(request) {
  const form = await request.formData();

  const fromUrl = form.get('fromUrl');
  const toPathname = form.get('toPathname');

  const blob = await copy(fromUrl, toPathname, { access: 'private' /* or 'public' */ });

  return Response.json(blob);
}
```

```ts filename="app/copy-blob/route.ts" framework=nextjs
import { copy } from '@vercel/blob';

export async function PUT(request: Request) {
  const form = await request.formData();

  const fromUrl = form.get('fromUrl') as string;
  const toPathname = form.get('toPathname') as string;

  const blob = await copy(fromUrl, toPathname, { access: 'private' /* or 'public' */ });

  return Response.json(blob);
}
```

```js filename="app/copy-blob/route.js" framework=nextjs
import { copy } from '@vercel/blob';

export async function PUT(request) {
  const form = await request.formData();

  const fromUrl = form.get('fromUrl');
  const toPathname = form.get('toPathname');

  const blob = await copy(fromUrl, toPathname, { access: 'private' /* or 'public' */ });

  return Response.json(blob);
}
```

```ts filename="api/copy-blob.ts" framework=other
import { copy } from '@vercel/blob';

export async function PUT(request: Request) {
  const form = await request.formData();

  const fromUrl = form.get('fromUrl') as string;
  const toPathname = form.get('toPathname') as string;

  const blob = await copy(fromUrl, toPathname, { access: 'private' /* or 'public' */ });

  return Response.json(blob);
}
```

```js filename="api/copy-blob.js" framework=other
import { copy } from '@vercel/blob';

export async function PUT(request) {
  const form = await request.formData();

  const fromUrl = form.get('fromUrl');
  const toPathname = form.get('toPathname');

  const blob = await copy(fromUrl, toPathname, { access: 'private' /* or 'public' */ });

  return Response.json(blob);
}
```

### `copy()`

The `copy` method copies an existing blob object to a new path inside the blob store.

The `contentType` and `cacheControlMaxAge` will not be copied from the source blob. If the values should be carried over to the copy, they need to be defined again in the options object.

Contrary to `put()`, `addRandomSuffix` is false by default. This means no automatic random id suffix is added to your blob url, unless you pass `addRandomSuffix: true`.

**TypeScript**

```js
copy(fromUrlOrPathname, toPathname, options);
```

**Python**

```python
copy(
    src_path: str,
    dst_path: str,
    *,
    access: Literal['private', 'public'],
    content_type: str | None = None,
    add_random_suffix: bool = False,
    overwrite: bool = False,
    cache_control_max_age: int | None = None,
    token: str | None = None,
) -> PutBlobResult
```

It accepts the following parameters:

- `fromUrlOrPathname`: (Required) A blob URL or pathname identifying an already existing blob
- `toPathname`: (Required) A string specifying the new path inside the blob store. This will be the base value of the return URL
- `options`: (Required) A `JSON` object with the following required and optional parameters:

| Parameter            | Required | Values                                                                                                                                                                                                                                                                                                       |
| -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `access`             | Yes      | [`'private'` or `'public'`](/docs/vercel-blob#private-and-public-storage). Determines the access level of the blob.                                                                                                                                                                                          |
| `contentType`        | No       | A string indicating the [media type](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type). By default, it's extracted from the toPathname's extension.                                                                                                                                          |
| `token`              | No       | A static read-write token. Defaults to `process.env.BLOB_READ_WRITE_TOKEN`. Its default value is not used when OIDC credentials are present, but an explicitly passed token always takes priority. See [Authentication](#authentication). |
| `oidcToken`          | No       | A Vercel OIDC token, used in place of `process.env.VERCEL_OIDC_TOKEN`. Pair with `storeId` (or `BLOB_STORE_ID`). Useful when your framework does not load `.env.local` into `process.env` automatically. An explicitly passed token is not refreshed automatically. See [Authentication](#authentication). |
| `storeId`            | No       | The Blob store id, used with OIDC. Defaults to `process.env.BLOB_STORE_ID`. The SDK accepts either `store_<id>` or `<id>` form. See [Authentication](#authentication). |
| `addRandomSuffix`    | No       | A boolean specifying whether to add a random suffix to the pathname. It defaults to `false`. |
| `allowOverwrite`     | No       | A boolean to allow overwriting blobs. By default an error will be thrown if you try to overwrite a blob by using the same `pathname` for multiple blobs.                                                                                                                                                     |
| `cacheControlMaxAge` | No       | A number in seconds to configure the edge and browser cache. Defaults to one month. See the [caching](/docs/vercel-blob#caching) documentation for more details.                                                                                                                                    |
| `ifMatch`            | No       | An ETag value. The copy only succeeds if the source blob's current ETag matches this value. Use this for [conditional writes](/docs/vercel-blob#conditional-writes) to prevent copying a blob that has been modified since you last read it. Throws `BlobPreconditionFailedError` if the ETag doesn't match. |
| `abortSignal`        | No       | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the operation                                                                                                                                                                                                       |

`copy()` returns a `JSON` object with the following data for the copied blob object:

**TypeScript**

```ts
{
  pathname: string;
  contentType: string;
  contentDisposition: string;
  url: string;
  downloadUrl: string;
  etag: string;
}
```

**Python**

```python
result.pathname             # str
result.content_type         # str
result.content_disposition  # str
result.url                  # str
result.download_url         # str
result.etag                 # str
```

An example blob is:

```json
{
  "pathname": "profilesv1/user-12345-copy.txt",
  "contentType": "text/plain",
  "contentDisposition": "attachment; filename=\"user-12345-copy.txt\"",
  "url": "https://ce0rcu23vrrdzqap.public.blob.vercel-storage.com/profilesv1/user-12345-copy.txt",
  "downloadUrl": "https://ce0rcu23vrrdzqap.public.blob.vercel-storage.com/profilesv1/user-12345-copy.txt?download=1",
  "etag": "\"a1b2c3d4e5f6\""
}
```

## Rename a blob

This example renames an existing blob to a new path in the store:

```ts
import { rename } from '@vercel/blob';

const blob = await rename('user-uploads/avatar-old.png', 'user-uploads/avatar.png', {
  access: 'private', // or 'public'
});
```

### `rename()`

The `rename` method moves an existing blob object to a new path inside the blob store. It copies the blob to the new path, then deletes the source blob. The source blob is only deleted after the copy succeeds. If the copy fails, the rename aborts and the source blob is untouched.

Like `copy()`, the `contentType` and `cacheControlMaxAge` will not be carried over from the source blob. If the values should be kept on the renamed blob, they need to be defined again in the options object.

By default, `rename()` throws an error if a blob already exists at `toPathname`. Pass `allowOverwrite: true` to replace it, or `addRandomSuffix: true` to generate a unique pathname instead.

If the source blob cannot be deleted after a successful copy, the method throws and the blob exists at both paths. Retry the rename with `allowOverwrite: true` to complete it.

`rename()` cannot be called with a [client token](#client-uploads).

```js
rename(fromUrlOrPathname, toPathname, options);
```

It accepts the following parameters:

- `fromUrlOrPathname`: (Required) A blob URL or pathname identifying an already existing blob
- `toPathname`: (Required) A string specifying the new path inside the blob store. This will be the base value of the return URL
- `options`: (Required) A `JSON` object with the following required and optional parameters:

| Parameter            | Required | Values                                                                                                                                                                                                                                                                                                            |
| -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `access`             | Yes      | [`'private'` or `'public'`](/docs/vercel-blob#private-and-public-storage). Determines the access level of the blob.                                                                                                                                                                                               |
| `contentType`        | No       | A string indicating the [media type](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type). By default, it's extracted from the toPathname's extension.                                                                                                                                               |
| `token`              | No       | A static read-write token. Defaults to `process.env.BLOB_READ_WRITE_TOKEN`. Its default value is not used when OIDC credentials are present, but an explicitly passed token always takes priority. See [Authentication](#authentication). |
| `oidcToken`          | No       | A Vercel OIDC token, used in place of `process.env.VERCEL_OIDC_TOKEN`. Pair with `storeId` (or `BLOB_STORE_ID`). Useful when your framework does not load `.env.local` into `process.env` automatically. An explicitly passed token is not refreshed automatically. See [Authentication](#authentication). |
| `storeId`            | No       | The Blob store id, used with OIDC. Defaults to `process.env.BLOB_STORE_ID`. The SDK accepts either `store_<id>` or `<id>` form. See [Authentication](#authentication). |
| `addRandomSuffix`    | No       | A boolean specifying whether to add a random suffix to the pathname. It defaults to `false`. |
| `allowOverwrite`     | No       | A boolean to allow overwriting blobs. By default an error will be thrown if a blob already exists at `toPathname`.                                                                                                                                                                                                |
| `cacheControlMaxAge` | No       | A number in seconds to configure the edge and browser cache. Defaults to one month. See the [caching](/docs/vercel-blob#caching) documentation for more details.                                                                                                                                         |
| `ifMatch`            | No       | An ETag value. The rename only succeeds if the source blob's current ETag matches this value. Use this for [conditional writes](/docs/vercel-blob#conditional-writes) to prevent renaming a blob that has been modified since you last read it. Throws `BlobPreconditionFailedError` if the ETag doesn't match. |
| `abortSignal`        | No       | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the operation                                                                                                                                                                                                            |

`rename()` returns a `JSON` object with the following data for the renamed blob object:

```ts
{
  pathname: string;
  contentType: string;
  contentDisposition: string;
  url: string;
  downloadUrl: string;
  etag: string;
}
```

An example blob is:

```json
{
  "pathname": "profilesv1/user-12345-renamed.txt",
  "contentType": "text/plain",
  "contentDisposition": "attachment; filename=\"user-12345-renamed.txt\"",
  "url": "https://ce0rcu23vrrdzqap.public.blob.vercel-storage.com/profilesv1/user-12345-renamed.txt",
  "downloadUrl": "https://ce0rcu23vrrdzqap.public.blob.vercel-storage.com/profilesv1/user-12345-renamed.txt?download=1",
  "etag": "\"a1b2c3d4e5f6\""
}
```

## Multipart Uploads

When uploading large files you should use multipart uploads to have a more reliable upload process. A multipart upload splits the file into multiple parts, uploads them in parallel and retries failed parts.
This process consists of three phases: creating a multipart upload, uploading the parts and completing the upload. `@vercel/blob` offers three different ways to create multipart uploads:

### Automatic

This method has everything baked in and is easiest to use. It's part of the `put` and `upload` API's. Under the hood it will start the upload, split your file into multiple parts with the same size, upload them in parallel and complete the upload.

**TypeScript**

```js
const blob = await put('large-movie.mp4', file, {
  access: 'private' /* or 'public' */,
  multipart: true,
});
```

**Python**

```python
from vercel.blob import BlobClient

client = BlobClient()

with open("large-movie.mp4", "rb") as f:
    file_data = f.read()

blob = client.put(
    "videos/large-movie.mp4",
    file_data,
    access="private",  # or "public",
    content_type="video/mp4",
)
```

### Manual

This method gives you full control over the multipart upload process. It consists of three phases:

**Phase 1: Create a multipart upload**

**TypeScript**

```js
const multipartUpload = await createMultipartUpload(pathname, options);
```

**Python**

```python
from vercel.blob import create_multipart_upload

multipart_upload = create_multipart_upload(
    "videos/intro.mp4",
    access="private",  # or "public",
    content_type="video/mp4",
    add_random_suffix=True,
)
```

`createMultipartUpload` accepts the following parameters:

- `pathname`: (Required) A string specifying the path inside the blob store. This will be the base value of the return URL and includes the filename and extension.
- `options`: (Required) A `JSON` object with the following required and optional parameters:

| Parameter            | Required | Values                                                                                                                                                                                                                                                                                        |
| -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `access`             | Yes      | [`'private'` or `'public'`](/docs/vercel-blob#private-and-public-storage). Determines the access level of the blob.                                                                                                                                                                           |
| `contentType`        | No       | The [media type](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type) for the file. If not specified, it's derived from the file extension. Falls back to `application/octet-stream` when no extension exists or can't be matched.                                               |
| `token`              | No       | A static read-write token. Defaults to `process.env.BLOB_READ_WRITE_TOKEN`. Its default value is not used when OIDC credentials are present, but an explicitly passed token always takes priority. You can also pass a client token created with `generateClientTokenFromReadWriteToken`. See [Authentication](#authentication). |
| `oidcToken`          | No       | A Vercel OIDC token, used in place of `process.env.VERCEL_OIDC_TOKEN`. Pair with `storeId` (or `BLOB_STORE_ID`). Useful when your framework does not load `.env.local` into `process.env` automatically. An explicitly passed token is not refreshed automatically. See [Authentication](#authentication). |
| `storeId`            | No       | The Blob store id, used with OIDC. Defaults to `process.env.BLOB_STORE_ID`. The SDK accepts either `store_<id>` or `<id>` form. See [Authentication](#authentication). |
| `addRandomSuffix`    | No       | A boolean specifying whether to add a random suffix to the pathname. It defaults to `true`.                                                                                                                                                                                                   |
| `cacheControlMaxAge` | No       | A number in seconds to configure the edge and browser cache. Defaults to one month. See the [caching](/docs/vercel-blob#caching) documentation for more details.                                                                                                                     |
| `abortSignal`        | No       | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the operation                                                                                                                                                                                        |

`createMultipartUpload()` returns a `JSON` object with the following data for the created upload:

```json
{
  "key": "string",
  "uploadId": "string"
}
```

**Phase 2: Upload all the parts**

> **💡 Note:** In the multipart uploader process, it's necessary for you to manage both
> memory usage and concurrent upload requests. Additionally, each part must be a
> minimum of 5MB, except the last one which can be smaller, and all parts should
> be of equal size.

**TypeScript**

```js
const part = await uploadPart(pathname, chunkBody, options);
```

**Python**

```python
from vercel.blob import upload_part

chunk1 = file_data[0:5*1024*1024] # minimum 5MB each, except last part
part = upload_part(
    "videos/intro.mp4",
    chunk1,
    access="private",  # or "public",
    upload_id=multipart_upload.upload_id,
    key=multipart_upload.key,
    part_number=1,
)
```

`uploadPart` accepts the following parameters:

- `pathname`: (Required) Same value as the `pathname` parameter passed to `createMultipartUpload`
- `chunkBody`: (Required) A blob object as `ReadableStream`, `String`, `ArrayBuffer` or `Blob` based on these [supported body types](https://developer.mozilla.org/docs/Web/API/fetch#body)
- `options`: (Required) A `JSON` object with the following required and optional parameters:

| Parameter     | Required | Values                                                                                                                                                                                                                                                                                        |
| ------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `access`      | Yes      | [`'private'` or `'public'`](/docs/vercel-blob#private-and-public-storage). Determines the access level of the blob.                                                                                                                                                                           |
| `partNumber`  | Yes      | A number identifying which part is uploaded                                                                                                                                                                                                                                                   |
| `key`         | Yes      | A string returned from `createMultipartUpload` which identifies the blob object                                                                                                                                                                                                               |
| `uploadId`    | Yes      | A string returned from `createMultipartUpload` which identifies the multipart upload                                                                                                                                                                                                          |
| `token`       | No       | A static read-write token. Defaults to `process.env.BLOB_READ_WRITE_TOKEN`. Its default value is not used when OIDC credentials are present, but an explicitly passed token always takes priority. You can also pass a client token created with `generateClientTokenFromReadWriteToken`. See [Authentication](#authentication). |
| `oidcToken`   | No       | A Vercel OIDC token, used in place of `process.env.VERCEL_OIDC_TOKEN`. Pair with `storeId` (or `BLOB_STORE_ID`). Useful when your framework does not load `.env.local` into `process.env` automatically. An explicitly passed token is not refreshed automatically. See [Authentication](#authentication). |
| `storeId`     | No       | The Blob store id, used with OIDC. Defaults to `process.env.BLOB_STORE_ID`. The SDK accepts either `store_<id>` or `<id>` form. See [Authentication](#authentication). |
| `abortSignal` | No       | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the operation                                                                                                                                                                                        |

`uploadPart()` returns a `JSON` object with the following data for the uploaded part:

```json
{
  "etag": "string",
  "partNumber": "number"
}
```

**Phase 3: Complete the multipart upload**

**TypeScript**

```js
const blob = await completeMultipartUpload(pathname, parts, options);
```

**Python**

```python
from vercel.blob import complete_multipart_upload

blob = complete_multipart_upload(
    "videos/intro.mp4",
    [part1, part2, part3],
    access="private",  # or "public",
    upload_id=multipart_upload.upload_id,
    key=multipart_upload.key,
)
```

`completeMultipartUpload` accepts the following parameters:

- `pathname`: (Required) Same value as the `pathname` parameter passed to `createMultipartUpload`
- `parts`: (Required) An array containing all the uploaded parts
- `options`: (Required) A `JSON` object with the following required and optional parameters:

| Parameter            | Required | Values                                                                                                                                                                                                                                                                                        |
| -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `access`             | Yes      | [`'private'` or `'public'`](/docs/vercel-blob#private-and-public-storage). Determines the access level of the blob.                                                                                                                                                                           |
| `key`                | Yes      | A string returned from `createMultipartUpload` which identifies the blob object                                                                                                                                                                                                               |
| `uploadId`           | Yes      | A string returned from `createMultipartUpload` which identifies the multipart upload                                                                                                                                                                                                          |
| `contentType`        | No       | The [media type](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type) for the file. If not specified, it's derived from the file extension. Falls back to `application/octet-stream` when no extension exists or can't be matched.                                               |
| `token`              | No       | A static read-write token. Defaults to `process.env.BLOB_READ_WRITE_TOKEN`. Its default value is not used when OIDC credentials are present, but an explicitly passed token always takes priority. You can also pass a client token created with `generateClientTokenFromReadWriteToken`. See [Authentication](#authentication). |
| `oidcToken`          | No       | A Vercel OIDC token, used in place of `process.env.VERCEL_OIDC_TOKEN`. Pair with `storeId` (or `BLOB_STORE_ID`). Useful when your framework does not load `.env.local` into `process.env` automatically. An explicitly passed token is not refreshed automatically. See [Authentication](#authentication). |
| `storeId`            | No       | The Blob store id, used with OIDC. Defaults to `process.env.BLOB_STORE_ID`. The SDK accepts either `store_<id>` or `<id>` form. See [Authentication](#authentication). |
| `addRandomSuffix`    | No       | A boolean specifying whether to add a random suffix to the pathname. It defaults to `true`.                                                                                                                                                                                                   |
| `cacheControlMaxAge` | No       | A number in seconds to configure the edge and browser cache. Defaults to one month. See the [caching](/docs/vercel-blob#caching) documentation for more details.                                                                                                                     |
| `abortSignal`        | No       | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the operation                                                                                                                                                                                        |

`completeMultipartUpload()` returns a `JSON` object with the following data for the created blob object:

```json
{
  "pathname": "string",
  "contentType": "string",
  "contentDisposition": "string",
  "url": "string",
  "downloadUrl": "string",
  "etag": "string"
}
```

### Uploader

A less verbose way than the manual process is the multipart uploader method. It's a wrapper around the manual multipart upload process and takes care of the data that is the same for all the three multipart phases.
This results in a simpler API, but still requires you to handle memory usage and concurrent upload requests.

**Phase 1: Create the multipart uploader**

**TypeScript**

```js
const uploader = await createMultipartUploader(pathname, options);
```

**Python**

```python
from vercel.blob import AsyncBlobClient

client = AsyncBlobClient()
uploader = await client.create_multipart_uploader(
    "examples/large-file.bin",
    content_type="application/octet-stream",
    add_random_suffix=True,
)
```

`createMultipartUploader` accepts the following parameters:

- `pathname`: (Required) A string specifying the path inside the blob store. This will be the base value of the return URL and includes the filename and extension.
- `options`: (Required) A `JSON` object with the following required and optional parameters:

| Parameter            | Required | Values                                                                                                                                                                                                                                                                                        |
| -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `access`             | Yes      | [`'private'` or `'public'`](/docs/vercel-blob#private-and-public-storage). Determines the access level of the blob.                                                                                                                                                                           |
| `contentType`        | No       | The [media type](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type) for the file. If not specified, it's derived from the file extension. Falls back to `application/octet-stream` when no extension exists or can't be matched.                                               |
| `token`              | No       | A static read-write token. Defaults to `process.env.BLOB_READ_WRITE_TOKEN`. Its default value is not used when OIDC credentials are present, but an explicitly passed token always takes priority. You can also pass a client token created with `generateClientTokenFromReadWriteToken`. See [Authentication](#authentication). |
| `oidcToken`          | No       | A Vercel OIDC token, used in place of `process.env.VERCEL_OIDC_TOKEN`. Pair with `storeId` (or `BLOB_STORE_ID`). Useful when your framework does not load `.env.local` into `process.env` automatically. An explicitly passed token is not refreshed automatically. See [Authentication](#authentication). |
| `storeId`            | No       | The Blob store id, used with OIDC. Defaults to `process.env.BLOB_STORE_ID`. The SDK accepts either `store_<id>` or `<id>` form. See [Authentication](#authentication). |
| `addRandomSuffix`    | No       | A boolean specifying whether to add a random suffix to the pathname. It defaults to `true`.                                                                                                                                                                                                   |
| `cacheControlMaxAge` | No       | A number in seconds to configure the edge and browser cache. Defaults to one month. See the [caching](/docs/vercel-blob#caching) documentation for more details.                                                                                                                     |
| `abortSignal`        | No       | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the operation                                                                                                                                                                                        |

`createMultipartUploader()` returns an `Uploader` object with the following attributes and methods:

**TypeScript**

```ts
{
  key: string;
  uploadId: string;
  uploadPart: (partNumber: number, body: BodyInit) => Promise<Part>;
  complete: (parts: Part[]) => Promise<PutBlobResult>;
}
```

**Python**

```python
uploader.upload_id                              # string
uploader.key                                    # string
uploader.upload_part(part_number, chunk_body)   # method
uploader.complete(parts)                        # method
```

**Phase 2: Upload all the parts**

> **💡 Note:** In the multipart uploader process, it's necessary for you to manage both
> memory usage and concurrent upload requests. Additionally, each part must be a
> minimum of 5MB, except the last one which can be smaller, and all parts should
> be of equal size.

**TypeScript**

```js
const part1 = await uploader.uploadPart(1, chunkBody1);
const part2 = await uploader.uploadPart(2, chunkBody2);
const part3 = await uploader.uploadPart(3, chunkBody3);
```

**Python**

```python
import asyncio
tasks = [
    uploader.upload_part(1, chunk_body_1),
    uploader.upload_part(2, chunk_body_2),
    uploader.upload_part(3, chunk_body_3),
]
parts = await asyncio.gather(*tasks)
```

`uploader.uploadPart` accepts the following parameters:

- `partNumber`: (Required) A number identifying which part is uploaded
- `chunkBody`: (Required) A blob object as `ReadableStream`, `String`, `ArrayBuffer` or `Blob` based on these [supported body types](https://developer.mozilla.org/docs/Web/API/fetch#body)

`uploader.uploadPart()` returns an object with the following data for the uploaded part:

**TypeScript**

```ts
{
  etag: string;
  partNumber: number;
}
```

**Python**

```python
part.etag         # string
part.part_number  # int
```

**Phase 3: Complete the multipart upload**

**TypeScript**

```js
const blob = await uploader.complete([part1, part2, part3]);
```

**Python**

```python
blob = await uploader.complete([part_1, part_2, part_3])
```

`uploader.complete` accepts the following parameters:

- `parts`: (Required) An array containing all the uploaded parts

`uploader.complete()` returns an object with the following data for the created blob object:

**TypeScript**

```ts
{
  pathname: string;
  contentType: string;
  contentDisposition: string;
  url: string;
  downloadUrl: string;
  etag: string;
}
```

**Python**

```python
result.pathname             # string
result.content_type         # string
result.content_disposition  # string
result.url                  # string
result.download_url         # string
result.etag                 # string
```

## Signed URLs

Vercel Signed URLs grant time-limited access to a blob URL without exposing a read-write token: your server issues a short-lived signed token, then any environment signs URLs for individual operations. Both methods are available under `@vercel/blob`. This section is a summary; the full parameter reference and examples live in [Vercel Signed URLs](/docs/vercel-blob/vercel-signed-urls).

### `issueSignedToken()`

The `issueSignedToken` method runs on your server and asks the Blob API for short-lived signing material. It uses the same [authentication](#authentication) as the rest of the SDK, so it works with OIDC or a read-write token.

```js
issueSignedToken(options);
```

You can scope the token with `pathname`, `operations` (`'get'`, `'head'`, `'put'`, or `'delete'`), `validUntil`, `allowedContentTypes`, and `maximumSizeInBytes`. It returns `{ delegationToken, clientSigningToken, validUntil }`. Treat the `clientSigningToken` as a secret: anyone who holds it can sign URLs within the delegation's scope. See the [full parameter reference](/docs/vercel-blob/vercel-signed-urls#issuesignedtoken).

### `presignUrl()`

The `presignUrl` method takes the material returned by `issueSignedToken` and produces a ready-to-fetch URL for a specific pathname and operation. It signs locally with no network call, so it runs anywhere: server, edge, or browser.

```js
presignUrl(signedToken, options);
```

See the [full parameter reference and an example for each operation](/docs/vercel-blob/vercel-signed-urls#presignurl).

## Client uploads

As seen in the [client uploads quickstart docs](/docs/vercel-blob/client-upload), you can upload files directly from clients (like browsers) to the Blob store.

All client uploads related methods are available under `@vercel/blob/client`.

### `upload()`

The `upload` method is dedicated to [client uploads](/docs/vercel-blob/client-upload). It fetches a client token on your server using the `handleUploadUrl` before uploading the blob. Read the [client uploads](/docs/vercel-blob/client-upload) documentation to learn more.

```js
upload(pathname, body, options);
```

It accepts the following parameters:

- `pathname`: (Required) A string specifying the base value of the return URL
- `body`: (Required) A blob object as `ReadableStream`, `String`, `ArrayBuffer` or `Blob` based on these [supported body types](https://developer.mozilla.org/docs/Web/API/fetch#body)
- `options`: (Required) A `JSON` object with the following required and optional parameters:

| Parameter          | Required | Values                                                                                                                                                            |
| ------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `access`           | Yes      | [`'private'` or `'public'`](/docs/vercel-blob#private-and-public-storage). Determines the access level of the blob.                                               |
| `contentType`      | No       | A string indicating the [media type](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type). By default, it's extracted from the pathname's extension. |
| `handleUploadUrl`  | Yes\*    | A string specifying the route to call for generating client tokens for [client uploads](/docs/vercel-blob/client-upload).                                 |
| `clientPayload`    | No       | A string to be sent to your `handleUpload` server code. Example use-case: attaching the post id an image relates to. So you can use it to update your database.   |
| `multipart`        | No       | Pass `multipart: true` when uploading large files. It will split the file into multiple parts, upload them in parallel and retry failed parts.                    |
| `abortSignal`      | No       | An [AbortSignal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) to cancel the operation                                                            |
| `onUploadProgress` | No       | Callback to track upload progress: `onUploadProgress({loaded: number, total: number, percentage: number})`                                                        |

`upload()` returns a `JSON` object with the following data for the created blob object:

```ts
{
  pathname: string;
  contentType: string;
  contentDisposition: string;
  url: string;
  downloadUrl: string;
  etag: string;
}
```

An example `url` is:

```
https://ce0rcu23vrrdzqap.public.blob.vercel-storage.com/profilesv1/user-12345-NoOVGDVcqSPc7VYCUAGnTzLTG2qEM2.txt
```

### `uploadPresigned()`

The `uploadPresigned` method is the presigned counterpart of [`upload`](#upload). Instead of fetching a client token, it asks your server for a presigned `PUT` URL and uploads the file directly to Blob storage, with no bearer token in flight.

```js
uploadPresigned(pathname, body, options);
```

It accepts the same parameters as `upload` and returns the same blob object, with one difference: point `handleUploadUrl` at a route that implements [`handleUploadPresigned`](#handleuploadpresigned) instead of `handleUpload`. See [Presigned uploads](/docs/vercel-blob/vercel-signed-urls#presigned-uploads) for the full flow.

### `handleUpload()`

A server-side route helper to manage client uploads, it has two responsibilities:

1. Generate tokens for client uploads
2. Listen for completed client uploads, so you can update your database with the URL of the uploaded file for example

```js
handleUpload(options);
```

It accepts the following parameters:

- `options`: (Required) A `JSON` object with the following parameters:

| Parameter                                         | Required | Values                                                                                                                                                                                                 |
| ------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `token`                                           | No       | A static read-write token used to verify and sign client uploads. Defaults to `process.env.BLOB_READ_WRITE_TOKEN`. OIDC tokens are not sufficient for `handleUpload`; use [`handleUploadPresigned`](#handleuploadpresigned) for an OIDC-compatible flow. |
| `request`                                         | Yes      | An `IncomingMessage` or `Request` object to be used to determine the action to take                                                                                                                    |
| [`onBeforeGenerateToken`](#onbeforegeneratetoken) | Yes      | A function to be called right before generating client tokens for client uploads. See below for usage                                                                                                  |
| [`onUploadCompleted`](#onuploadcompleted)         | Yes      | A function to be called by Vercel Blob when the client upload finishes. This is useful to update your database with the blob url that was uploaded                                                     |
| `body`                                            | Yes      | The request body                                                                                                                                                                                       |

`handleUpload()` returns:

```ts
Promise<
  | { type: 'blob.generate-client-token'; clientToken: string }
  | { type: 'blob.upload-completed'; response: 'ok' }
>;
```

#### `onBeforeGenerateToken()`

The `onBeforeGenerateToken` function runs on your server before the SDK generates a client token. **You must authenticate and authorize the user inside this function.** If you skip this step, your upload route allows anonymous uploads to your Blob store.

The function receives the following arguments:

- `pathname`: The destination path for the blob
- `clientPayload`: A string payload specified on the client when calling `upload()`
- `multipart`: A boolean specifying whether the file is a multipart upload.

The function must return an object with the following properties:

| Parameter             | Required | Values                                                                                                                                                                                                                    |
| --------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `addRandomSuffix`     | No       | A boolean specifying whether to add a random suffix to the `pathname`. It defaults to `false`. **We recommend using this option** to ensure there are no conflicts in your blob filenames.                                |
| `allowedContentTypes` | No       | An array of strings specifying the [media type](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Type) that are allowed to be uploaded. By default, it's all content types. Wildcards are supported (`text/*`) |
| `maximumSizeInBytes`  | No       | A number specifying the maximum size in bytes that can be uploaded. The maximum is 5TB.                                                                                                                                   |
| `validUntil`          | No       | A number specifying the timestamp in ms when the token will expire. By default, it's now + 1 hour.                                                                                                                        |
| `allowOverwrite`      | No       | A boolean to allow overwriting blobs. By default an error will be thrown if you try to overwrite a blob by using the same `pathname` for multiple blobs.                                                                  |
| `cacheControlMaxAge`  | No       | A number in seconds to configure how long Blobs are cached. Defaults to one month. Cannot be set to a value lower than 1 minute. See the [caching](/docs/vercel-blob#caching) documentation for more details.    |
| `callbackUrl`         | No       | A string specifying the URL that Vercel Blob will call when the upload completes. See [client uploads](/docs/vercel-blob/client-upload) for examples.                                                             |
| `tokenPayload`        | No       | A string specifying a payload to be sent to your server on upload completion.                                                                                                                                             |

#### `onUploadCompleted()`

The `onUploadCompleted` function receives the following arguments:

- `blob`: The blob that was uploaded. See the return type of [`put()`](#put) for more details.
- `tokenPayload`: The payload that was defined in the [`onBeforeGenerateToken()`](#onbeforegeneratetoken) function.

### `handleUploadPresigned()`

The `handleUploadPresigned` server-side route helper is the presigned counterpart of [`handleUpload`](#handleupload). Instead of signing client tokens with a read-write token, it returns presigned `PUT` URLs backed by [`issueSignedToken`](#issuesignedtoken), so the route works with OIDC as well as a read-write token.

```js
handleUploadPresigned(options);
```

Key differences from `handleUpload`:

- You mint the signed token inside a `getSignedToken` callback, typically with `issueSignedToken({ pathname, operations: ['put'] })`. **You must authenticate and authorize the user inside this function**, just like in `onBeforeGenerateToken`.
- Upload constraints such as `allowedContentTypes` and `maximumSizeInBytes` move into the `urlOptions` object returned by `getSignedToken`.
- The `onUploadCompleted` callback keeps the same shape, but its signature is verified with a webhook public key (the `webhookPublicKey` parameter, which defaults to `process.env.BLOB_WEBHOOK_PUBLIC_KEY`) instead of the read-write token.

See the [full parameter reference, an example route handler, and migration steps](/docs/vercel-blob/vercel-signed-urls#handleuploadpresigned).

### Client uploads routes

Here's an example Next.js App Router route handler that uses `handleUpload()`:

```ts filename="app/api/post/upload/route.ts"
import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';
import { NextResponse } from 'next/server';
import { auth } from '@/lib/auth';

export async function POST(request: Request): Promise<NextResponse> {
  const body = (await request.json()) as HandleUploadBody;

  try {
    const jsonResponse = await handleUpload({
      body,
      request,
      onBeforeGenerateToken: async (pathname, clientPayload) => {
        // Authenticate and authorize users before generating the token.
        // Without this check, anyone can upload to your Blob store.
        const session = await auth();
        if (!session) {
          throw new Error('Not authenticated');
        }

        // When using clientPayload, validate it to prevent users from
        // modifying other users' data
        const { postId } = JSON.parse(clientPayload || '{}');

        return {
          allowedContentTypes: ['image/jpeg', 'image/png', 'image/webp'],
          tokenPayload: JSON.stringify({
            userId: session.user.id,
            postId,
          }),
        };
      },
      onUploadCompleted: async ({ blob, tokenPayload }) => {
        // This callback won't fire on localhost.
        // Use ngrok or similar for the full upload flow locally.

        console.log('blob upload completed', blob, tokenPayload);

        try {
          const { userId, postId } = JSON.parse(tokenPayload);
          // Safely update your database since the user was already authenticated
          // await db.update({ imageUrl: blob.url, postId, userId });
        } catch (error) {
          throw new Error('Could not update post');
        }
      },
    });

    return NextResponse.json(jsonResponse);
  } catch (error) {
    return NextResponse.json(
      { error: error instanceof Error ? error.message : String(error) },
      { status: 400 },
    );
  }
}
```

## Handling errors

When you make a request to the SDK using any of the above methods, they will return an error if the request fails due to any of the following reasons:

- Missing required parameters
- An invalid token or a token that does not have access to the Blob object
- Suspended Blob store
- Blob file or Blob store not found
- Precondition failed (when using `ifMatch` for [conditional writes](/docs/vercel-blob#conditional-writes) and the ETag doesn't match)
- Unforeseen or unknown errors

To catch these errors, wrap your requests with a `try/catch` statement as shown below:

**TypeScript**

```js
import { put, BlobAccessError } from '@vercel/blob';

try {
  await put(...);
} catch (error) {
  if (error instanceof BlobAccessError) {
    // handle a recognized error
  } else {
    // throw the error again if it's unknown
  throw error;
  }
}

```

**Python**

```python
from vercel.blob import BlobClient
from vercel.blob.errors import BlobError, BlobNotFoundError

try:
    client = BlobClient()
    client.put(
        "examples/file.txt", b"hello",
        access="private",  # or "public"
    )
except BlobNotFoundError:
    # handle a recognized error
    ...
except BlobError as e:
    # handle other blob errors
    ...
except Exception as e:
    # handle unknown errors
    ...

```


---

[View full sitemap](/docs/sitemap)
