This site is not affiliated with or endorsed by Vercel, Inc. Read the docs, then deploy or run each experiment yourself.
Platform

Blob Storage Lab

Upload, list, and delete objects with Vercel Blob - one-click store provisioning on Deploy.

Run this experiment yourself

Demos are not embedded on this site. Deploy a standalone copy on Vercel or run the experiment app locally.

Local development

cd apps/experiments/blob-storage-lab
pnpm install
pnpm dev

Then open http://localhost:3010.

This is an experimental demo. Use it as a starting point for your own projects.

The Blob Storage Lab shows the full lifecycle of a small public object on Vercel Blob: upload text from the browser, list recent uploads, open the CDN URL, and delete it. A single Route Handler (app/api/blob/upload/route.ts) wraps the @vercel/blob SDK (put, list, del), validates the input with Zod, and degrades to an honest 503 setup message when no Blob store is connected.

Features

  • Upload from the browser. The demo turns the textarea contents into a File (text/plain) and sends it as multipart/form-data.
  • List recent uploads. Shows up to 20 objects under the experiments/blob-lab/ prefix, with pathname, size, and upload time.
  • Delete with a guard. Only URLs containing /experiments/blob-lab/ can be deleted, so the demo cannot remove unrelated objects in the same store.
  • Hard limits. Uploads are capped at 256 KiB (262144 bytes) and filenames are restricted to [a-zA-Z0-9._-], 1–80 characters.
  • Honest missing-config state. Without BLOB_READ_WRITE_TOKEN, every method returns 503 with a setup hint and the UI shows a warning instead of failing silently.
  • One-click provisioning. The Deploy button passes a Blob store (access: "public") so the token is injected for you.

API Reference

All methods live at a single path and share the same availability check. If BLOB_READ_WRITE_TOKEN is unset, every method returns 503.

GET /api/blob/upload

Lists up to 20 blobs whose pathname starts with experiments/blob-lab/.

Prop

Type

POST /api/blob/upload

Uploads one file with multipart/form-data. The stored pathname is experiments/blob-lab/<Date.now()>-<filename> with access: "public" and addRandomSuffix: false.

Prop

Type

DELETE /api/blob/upload

Deletes one blob by URL. Send a JSON body.

Prop

Type

Implementation Details

demo.tsx
logic.ts
logic.test.ts
route.ts

Detect the service before touching the SDK

getServiceStatus('blob') in lib/vercel/services.ts reports Blob as available only when BLOB_READ_WRITE_TOKEN is set. Each handler checks it first and returns 503 otherwise.

const status = getServiceStatus('blob');
if (!status.available) {
  return NextResponse.json(
    {
      error: 'Vercel Blob is not configured',
      setup: 'Add BLOB_READ_WRITE_TOKEN or deploy with the Blob store in one click.',
    },
    { status: 503 },
  );
}

Validate size and filename

The size cap is a constant, and the filename is validated with Zod before any write.

const MAX_BYTES = 256 * 1024;

const metaSchema = z.object({
  filename: z
    .string()
    .min(1)
    .max(80)
    .regex(/^[a-zA-Z0-9._-]+$/),
});

apps/experiments/blob-storage-lab/logic.ts exports the same limit (MAX_UPLOAD_BYTES) and a matching isAllowedFilename() so the client can fail fast.

Write with put() under a fixed prefix

All uploads go under experiments/blob-lab/ with a timestamp so names do not collide.

const pathname = `experiments/blob-lab/${Date.now()}-${parsed.data.filename}`;
const blob = await put(pathname, file, {
  access: 'public',
  addRandomSuffix: false,
});

List and delete within the prefix

const { blobs } = await list({ prefix: 'experiments/blob-lab/', limit: 20 });
if (!url.data.url.includes('/experiments/blob-lab/')) {
  return NextResponse.json({ error: 'Can only delete blob-lab uploads' }, { status: 403 });
}

await del(url.data.url);

Drive it from the client

demo.tsx loads the list on mount, treats a 503 as "not configured", and builds the upload from the textarea.

const file = new File([text], filename, { type: 'text/plain' });
const form = new FormData();
form.set('file', file);
form.set('filename', filename);
const res = await fetch('/api/blob/upload', { method: 'POST', body: form });

Use Cases

  • Avatars, attachments, and user-generated assets that should be served from a CDN URL.
  • Build artifacts, exports, and generated files (reports, PDFs, JSON snapshots) produced by Route Handlers or cron jobs.
  • A reference for guarded storage routes: fixed prefix, size cap, filename allow-list, and prefix-checked delete.
  • A starting point before moving to client uploads for larger files.

Limitations

  • Small files only. The route rejects anything over 256 KiB with 413. Files go through your Route Handler, so this is not a large-file upload pattern.
  • Public access. Objects are written with access: "public", so anyone with the URL can read them. Do not upload secrets or personal data.
  • No authentication. The demo route has no user check. Anyone who can reach the deployment can upload and delete lab files.
  • Prefix check is a substring match. Delete authorization is url.includes('/experiments/blob-lab/'), which is enough for a demo but is not a strict ownership check.
  • List is capped. GET returns at most 20 items and does not paginate.
  • SDK errors are not caught. If the token is present but invalid, or the Blob service errors, the exception from put, list, or del is not handled by the route.

Use in your project

Minimal public upload route. Install the SDK with pnpm add @vercel/blob and provide BLOB_READ_WRITE_TOKEN.

// app/api/upload/route.ts
import { put } from '@vercel/blob';
import { NextResponse } from 'next/server';

const MAX_BYTES = 256 * 1024;

export async function POST(request: Request) {
  const form = await request.formData();
  const file = form.get('file');

  if (!(file instanceof File)) {
    return NextResponse.json({ error: 'file is required' }, { status: 400 });
  }
  if (file.size > MAX_BYTES) {
    return NextResponse.json({ error: `Max upload size is ${MAX_BYTES} bytes` }, { status: 413 });
  }

  const blob = await put(`uploads/${Date.now()}-${file.name}`, file, {
    access: 'public',
    addRandomSuffix: false,
  });

  return NextResponse.json({ url: blob.url, pathname: blob.pathname, size: file.size });
}

Add authentication and your own filename policy before using this in production.

Deployment

Deploy on Vercel

Use the one-click Deploy button below. It clones the repository and provisions a public Vercel Blob store, which injects BLOB_READ_WRITE_TOKEN into the new project.

The site-wide platform Deploy button also includes a Blob store, along with Upstash Redis and Neon.

Local Development

pnpm install
pnpm dev

Without a token the lab runs but shows the 503 warning. To use a real store, link the project and pull its variables (or paste the token manually):

vercel env pull .env.local
# or
echo 'BLOB_READ_WRITE_TOKEN=vercel_blob_rw_...' >> .env.local

Try the API directly (default port 3000):

# List
curl http://localhost:3000/api/blob/upload

# Upload
curl -X POST http://localhost:3000/api/blob/upload \
  -F "file=@./hello.txt;type=text/plain" \
  -F "filename=hello.txt"

# Delete (use a url returned by the upload or list call)
curl -X DELETE http://localhost:3000/api/blob/upload \
  -H "content-type: application/json" \
  -d '{"url":"https://<store>.public.blob.vercel-storage.com/experiments/blob-lab/<file>"}'

Expected response when no token is set:

{
  "error": "Vercel Blob is not configured",
  "setup": "Add BLOB_READ_WRITE_TOKEN or deploy with the Blob store in one click."
}

Configuration

From .env.example:

VariableRequiredPurpose
BLOB_READ_WRITE_TOKENYes, for live uploadsRead/write token for the Vercel Blob store. Injected automatically when the store is provisioned.

Constants in app/api/blob/upload/route.ts: MAX_BYTES = 256 * 1024, prefix experiments/blob-lab/, list limit: 20.

Vercel / Next.js Features Used

  • Vercel Blob (@vercel/blob): put, list, and del.
  • Route Handlers with GET, POST, and DELETE exports and request.formData().
  • Deploy Button stores parameter (BLOB_STORE in lib/vercel/deploy.ts) for one-click Blob provisioning.
  • Zod for filename and DELETE body validation.

Next Steps

On this page