Skip to content
Docs
 

Server Uploads with Vercel Blob

Vercel Blob is available on all plans

Those with the owner, member, developer role can access this feature

In this guide, you'll learn how to do the following:

  • Use the Vercel dashboard to create a Blob store connected to a project
  • Upload a file using the Blob SDK from the server

Vercel has a 4.5 MB request body size limit on Vercel Functions. If you need to upload larger files, use client uploads.

Vercel Blob works with any frontend framework. First, install the package:

Terminal
pnpm i @vercel/blob
Terminal
yarn add @vercel/blob
Terminal
npm i @vercel/blob
Terminal
bun add @vercel/blob
    1. Go to your project's Storage section in the sidebar
    2. Select Create Database, then choose Blob
    3. Select Continue, then set the access to Private or Public
    4. Use the name "Images" 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.

  1. Since you created the Blob store in a project, we automatically created and added the following Environment Variables to the project for you.

    By default, connected stores use OIDC-based authentication with short-lived, automatically rotated credentials:

    • BLOB_STORE_ID — identifies your Blob store
    • VERCEL_OIDC_TOKEN — a short-lived token issued at runtime; rotated automatically

    The following variable is also added as a fallback for code running outside Vercel or to generate client tokens for browser uploads:

    • BLOB_READ_WRITE_TOKEN — a long-lived static read-write token

    To use these Environment Variables locally, we recommend pulling them with the Vercel CLI:

    vercel env pull

    If the Blob variables don't show up in your .env.local file, your store connection likely doesn't include the Development environment, which is the one vercel env pull reads from. You can add it from the store's Projects tab: open the context menu (⋯) next to your project, select Update Project Connection, and include Development.

Server uploads are perfectly fine as long as you do not need to upload files larger than 4.5 MB on Vercel. If you need to upload larger files, consider using client uploads.

The following example shows how to use a Server Action with Next.js App Router to upload a file to Vercel Blob.

app/components/form.tsx
import { put } from '@vercel/blob';
import { revalidatePath } from 'next/cache';
 
export async function Form() {
  async function uploadImage(formData: FormData) {
    'use server';
    const imageFile = formData.get('image') as File;
    const blob = await put(imageFile.name, imageFile, {
      access: 'private' /* or 'public' */,
      addRandomSuffix: true,
    });
    revalidatePath('/');
    return blob;
  }
 
  return (
    <form action={uploadImage}>
      <label htmlFor="image">Image</label>
      <input
        type="file"
        id="image"
        name="image"
        accept="image/jpeg, image/png, image/webp"
        required
      />
      <button>Upload</button>
    </form>
  );
}

Read more about Server Actions and App Router on the Next.js documentation.

You can upload files to Vercel Blob using Route Handlers/API Routes. The following example shows how to upload a file to Vercel Blob using a server upload page and route.

  1. This page will upload files to your server. The files will then be sent to Vercel Blob.

    src/app/avatar/upload/page.tsx
    'use client';
     
    import type { PutBlobResult } from '@vercel/blob';
    import { useState, useRef } from 'react';
     
    export default function AvatarUploadPage() {
      const inputFileRef = useRef<HTMLInputElement>(null);
      const [blob, setBlob] = useState<PutBlobResult | null>(null);
      return (
        <>
          <h1>Upload Your Avatar</h1>
     
          <form
            onSubmit={async (event) => {
              event.preventDefault();
     
              if (!inputFileRef.current?.files) {
                throw new Error('No file selected');
              }
     
              const file = inputFileRef.current.files[0];
     
              const response = await fetch(
                `/api/avatar/upload?filename=${file.name}`,
                {
                  method: 'POST',
                  body: file,
                },
              );
     
              const newBlob = (await response.json()) as PutBlobResult;
     
              setBlob(newBlob);
            }}
          >
            <input
              name="file"
              ref={inputFileRef}
              type="file"
              accept="image/jpeg, image/png, image/webp"
              required
            />
            <button type="submit">Upload</button>
          </form>
          {blob && (
            <div>
              Blob url: <a href={blob.url}>{blob.url}</a>
            </div>
          )}
        </>
      );
    }
    app/avatar/upload/page.jsx
    'use client';
     
    import { useState, useRef } from 'react';
     
    export default function AvatarUploadPage() {
      const inputFileRef = useRef(null);
      const [blob, setBlob] = useState(null);
      return (
        <>
          <h1>Upload Your Avatar</h1>
     
          <form
            onSubmit={async (event) => {
              event.preventDefault();
     
              const file = inputFileRef.current.files[0];
     
              const response = await fetch(
                `/api/avatar/upload?filename=${file.name}`,
                {
                  method: 'POST',
                  body: file,
                },
              );
     
              const newBlob = await response.json();
     
              setBlob(newBlob);
            }}
          >
            <input
              name="file"
              ref={inputFileRef}
              type="file"
              accept="image/jpeg, image/png, image/webp"
              required
            />
            <button type="submit">Upload</button>
          </form>
          {blob && (
            <div>
              Blob url: <a href={blob.url}>{blob.url}</a>
            </div>
          )}
        </>
      );
    }
    pages/avatar/upload.tsx
    import type { PutBlobResult } from '@vercel/blob';
    import { useState, useRef } from 'react';
     
    export default function AvatarUploadPage() {
      const inputFileRef = useRef<HTMLInputElement>(null);
      const [blob, setBlob] = useState<PutBlobResult | null>(null);
      return (
        <>
          <h1>Upload Your Avatar</h1>
     
          <form
            onSubmit={async (event) => {
              event.preventDefault();
     
              if (!inputFileRef.current?.files) {
                throw new Error('No file selected');
              }
     
              const file = inputFileRef.current.files[0];
     
              const response = await fetch(
                `/api/avatar/upload?filename=${file.name}`,
                {
                  method: 'POST',
                  body: file,
                },
              );
     
              const newBlob = (await response.json()) as PutBlobResult;
     
              setBlob(newBlob);
            }}
          >
            <input
              name="file"
              ref={inputFileRef}
              type="file"
              accept="image/jpeg, image/png, image/webp"
              required
            />
            <button type="submit">Upload</button>
          </form>
          {blob && (
            <div>
              Blob url: <a href={blob.url}>{blob.url}</a>
            </div>
          )}
        </>
      );
    }
    pages/avatar/upload.jsx
    import { useState, useRef } from 'react';
     
    export default function AvatarUploadPage() {
      const inputFileRef = useRef(null);
      const [blob, setBlob] = useState(null);
      return (
        <>
          <h1>Upload Your Avatar</h1>
     
          <form
            onSubmit={async (event) => {
              event.preventDefault();
     
              const file = inputFileRef.current.files[0];
     
              const response = await fetch(
                `/api/avatar/upload?filename=${file.name}`,
                {
                  method: 'POST',
                  body: file,
                },
              );
     
              const newBlob = await response.json();
     
              setBlob(newBlob);
            }}
          >
            <input
              name="file"
              ref={inputFileRef}
              type="file"
              accept="image/jpeg, image/png, image/webp"
              required
            />
            <button type="submit">Upload</button>
          </form>
          {blob && (
            <div>
              Blob url: <a href={blob.url}>{blob.url}</a>
            </div>
          )}
        </>
      );
    }
  2. This route forwards the file to Vercel Blob and returns the URL of the uploaded file to the browser.

    src/app/api/avatar/upload/route.ts
    import { put } from '@vercel/blob';
    import { NextResponse } from 'next/server';
     
    export async function POST(request: Request): Promise<NextResponse> {
      const { searchParams } = new URL(request.url);
      const filename = searchParams.get('filename');
     
      const blob = await put(filename, request.body, {
        access: 'private' /* or 'public' */,
        addRandomSuffix: true,
      });
     
      return NextResponse.json(blob);
    }
    src/app/api/avatar/upload/route.js
    import { put } from '@vercel/blob';
    import { NextResponse } from 'next/server';
     
    export async function POST(request) {
      const { searchParams } = new URL(request.url);
      const filename = searchParams.get('filename');
     
      const blob = await put(filename, request.body, {
        access: 'private' /* or 'public' */,
        addRandomSuffix: true,
      });
     
      return NextResponse.json(blob);
    }
    pages/api/avatar/upload.ts
    import { put } from '@vercel/blob';
    import type { NextApiResponse, NextApiRequest, PageConfig } from 'next';
     
    export default async function handler(
      request: NextApiRequest,
      response: NextApiResponse,
    ) {
      const blob = await put(request.query.filename as string, request, {
        access: 'private' /* or 'public' */,
        addRandomSuffix: true,
      });
     
      return response.status(200).json(blob);
    }
     
    export const config: PageConfig = {
      api: {
        bodyParser: false,
      },
    };
    pages/api/avatar/upload.js
    import { put } from '@vercel/blob';
     
    export default async function handler(request, response) {
      const blob = await put(request.query.filename, request, {
        access: 'private' /* or 'public' */,
        addRandomSuffix: true,
      });
     
      return response.status(200).json(blob);
    }
     
    export const config = {
      api: {
        bodyParser: false,
      },
    };
  1. Run your application locally and visit /avatar/upload to upload the file to your store. The browser will display the unique URL created for the file.

    • Go to the Vercel Project where you created the store
    • Open Storage in the sidebar and select your new store
    • Paste the blob object URL returned in the previous step in the Blob URL input box in the Browser section and select Lookup
    • The following blob object metadata will be displayed: file name, path, size, uploaded date, content type and HTTP headers
    • You also have the option to download and delete the file from this page

You have successfully uploaded an object to your Vercel Blob store and are able to review its metadata, download, and delete it from your Vercel Storage Dashboard.

Last updated August 11, 2026

Was this helpful?

supported.