# Vercel Blob: client uploads vs server uploads and the 4.5 MB limit

> A Vercel Function accepts at most 4.5 MB of request body. Server uploads hit it; client uploads skip it. How client uploads work, and when a server upload is still right.

Your upload works on your laptop. You deploy. A 6 MB photo fails with
`413 FUNCTION_PAYLOAD_TOO_LARGE`.

That is the Vercel Function request body limit: **4.5 MB**. It is not
configurable. Any route that receives the file itself will hit it.

## Server uploads: the file goes through your function

```ts
// app/api/upload/route.ts: fine for small files, breaks at 4.5 MB
export async function POST(request: Request) {
  const form = await request.formData();
  const file = form.get("file") as File;
  const blob = await put(file.name, file, { access: "private" });
  return Response.json(blob);
}
```

Three costs, not one:

- **The limit.** Files over 4.5 MB fail.
- **Function time.** You pay for every second the function spends receiving.
- **Transfer.** Server uploads incur Fast Data Transfer on the way in. Client
  uploads have no transfer charge.

## Client uploads: the file goes straight to Blob

The browser asks your route for a short-lived **client token**, then sends the
bytes directly to Vercel Blob. Your function handles two small JSON requests.
Files up to 5 TB work.

```ts
// app/api/upload/route.ts
import { handleUpload, type HandleUploadBody } from "@vercel/blob/client";

export async function POST(request: Request) {
  const body = (await request.json()) as HandleUploadBody;
  const result = await handleUpload({
    body,
    request,
    onBeforeGenerateToken: async (pathname, clientPayload) => {
      const user = await requireUser(); // throw to refuse
      return {
        allowedContentTypes: ["image/jpeg", "image/png", "image/webp"],
        maximumSizeInBytes: 10 * 1024 * 1024,
        addRandomSuffix: false,
        allowOverwrite: false,
        validUntil: Date.now() + 15 * 60 * 1000,
        tokenPayload: JSON.stringify({ userId: user.id }),
      };
    },
    onUploadCompleted: async ({ blob, tokenPayload }) => {
      // Vercel calls this after the upload. Not on localhost: see below.
    },
  });
  return Response.json(result);
}
```

```tsx
"use client";
import { upload } from "@vercel/blob/client";

const blob = await upload(pathname, file, {
  access: "private",
  handleUploadUrl: "/api/upload",
  onUploadProgress: ({ percentage }) => setProgress(percentage),
});
```

## What the token enforces

The constraints in `onBeforeGenerateToken` are signed into the token. Vercel
Blob checks them at the store, not in the browser:

- `allowedContentTypes`: other types are refused.
- `maximumSizeInBytes`: bigger bodies are refused.
- `allowOverwrite: false`: an existing pathname is refused.
- `validUntil`: expired tokens are refused. The default is one hour. Use less.

That is why a client upload is safe even though the browser holds the file.
The token is a narrow credential, not the read-write token.

## Two things that catch people

- **You must authenticate in `onBeforeGenerateToken`.** Without a check there,
  anyone can upload to your store. Throwing refuses the token.
- **`handleUpload()` needs `BLOB_READ_WRITE_TOKEN`.** It signs client tokens
  with it. OIDC alone is not enough for this helper. (The newer
  `handleUploadPresigned()` works with OIDC and verifies callbacks with
  `BLOB_WEBHOOK_PUBLIC_KEY` instead.)

## When a server upload is still right

When your own code makes the bytes: a generated PDF, a resized thumbnail, an
export. Those never cross the body limit on the way in, because there is no
way in. Use `put()` from the server for those, and client uploads for
anything a user sends.

## Large files

Past about 100 MB, pass `multipart: true` to `upload()`. The SDK splits the
file into parts, uploads them in parallel and retries failed parts, instead
of restarting from zero. Each part is one advanced operation on your bill.

---

Agentic Boilerplate: A Next.js repo your agent already knows. Free during launch, then $99 once.

- Site map for agents: https://agenticboilerplate.com/llms.txt
- Public API: https://agenticboilerplate.com/openapi.json
- Contact: agenticstudio@gmail.com
