MCPInstall

vercel-blob

Integrate Vercel Blob object storage for file uploads, image management, and CDN-delivered assets in Next.js applications. Supports client-side uploads with presigned URLs and multipart transfers. Use when implementing file uploads (images, PDFs, videos), managing user-generated content, or troubleshooting missing tokens, size limit errors, or client upload failures.

Installs
63
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/ovachiever/droid-tings --skill vercel-blob
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads vercel-blob automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Vercel Blob (Object Storage)

Status: Production Ready Last Updated: 2025-10-29 Dependencies: None Latest Versions: @vercel/blob@2.0.0


Quick Start (3 Minutes)

1. Create Blob Store

# In Vercel dashboard: Storage → Create Database → Blob
vercel env pull .env.local

Creates: BLOB_READ_WRITE_TOKEN

2. Install

npm install @vercel/blob

3. Upload File (Server Action)

'use server';

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

export async function uploadFile(formData: FormData) {
  const file = formData.get('file') as File;

  const blob = await put(file.name, file, {
    access: 'public' // or 'private'
  });

  return blob.url; // https://xyz.public.blob.vercel-storage.com/file.jpg
}

CRITICAL:

  • Use client upload tokens for direct client uploads (don't expose BLOB_READ_WRITE_TOKEN)
  • Set correct access level (public vs private)
  • Files are automatically distributed via CDN

The 5-Step Setup Process

Step 1: Create Blob Store

Vercel Dashboard:

  1. Project → Storage → Create Database → Blob
  2. Copy BLOB_READ_WRITE_TOKEN

Local Development:

vercel env pull .env.local

Creates .env.local:

BLOB_READ_WRITE_TOKEN="vercel_blob_rw_xxx"

Key Points:

  • Free tier: 100GB bandwidth/month
  • File size limit: 500MB per file
  • Automatic CDN distribution
  • Public files are cached globally

Step 2: Server-Side Upload

Next.js Server Action:

'use server';

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

export async function uploadAvatar(formData: FormData) {
  const file = formData.get('avatar') as File;

  // Validate file
  if (!file.type.startsWith('image/')) {
    throw new Error('Only images allowed');
  }

  if (file.size > 5 * 1024 * 1024) {
    throw new Error('Max file size: 5MB');
  }

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

  return {
    url: blob.url,
    pathname: blob.pathname
  };
}

API Route (Edge Runtime):

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

export const runtime = 'edge';

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

  const blob = await put(file.name, file, { access: 'public' });

  return Response.json(blob);
}

Step 3: Client-Side Upload (Presigned URLs)

Create Upload Token (Server Action):

'use server';

import { handleUpload, type HandleUploadBody } from '@vercel/blob/client';

export async function getUploadToken(filename: string) {
  const jsonResponse = await handleUpload({
    body: {
      type: 'blob.generate-client-token',
      payload: {
        pathname: `uploads/${filename}`,
        access: 'public',
        onUploadCompleted: {
          callbackUrl: `${process.env.NEXT_PUBLIC_URL}/api/upload-complete`
        }
      }
    },
    request: new Request('https://dummy'),
    onBeforeGenerateToken: async (pathname) => {
      // Optional: validate user permissions
      return {
        allowedContentTypes: ['image/jpeg', 'image/png', 'image/webp'],
        maximumSizeInBytes: 5 * 1024 * 1024 // 5MB
      };
    },
    onUploadCompleted: async ({ blob, tokenPayload }) => {
      console.log('Upload completed:', blob.url);
    }
  });

  return jsonResponse;
}

Client Upload:

'use client';

import { upload } from '@vercel/blob/client';
import { getUploadToken } from './actions';

export function UploadForm() {
  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const form = e.currentTarget;
    const file = (form.elements.namedItem('file') as HTMLInputElement).files?.[0];

    if (!file) return;

    const tokenResponse = await getUploadToken(file.name);

    const blob = await upload(file.name, file, {
      access: 'public',
      handleUploadUrl: tokenResponse.url
    });

    console.log('Uploaded:', blob.url);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="file" name="file" required />
      <button type="submit">Upload</button>
    </form>
  );
}

Step 4: List, Download, Delete

List Files:

import { list } from '@vercel/blob';

const { blobs } = await list({
  prefix: 'avatars/',
  limit: 100
});

// Returns: { url, pathname, size, uploadedAt, ... }[]

List with Pagination:

let cursor: string | undefined;
const allBlobs = [];

do {
  const { blobs, cursor: nextCursor } = await list({
    prefix: 'uploads/',
    cursor
  });
  allBlobs.push(...blobs);
  cursor = nextCursor;
} while (cursor);

Download File:

// Files are publicly accessible if access: 'public'
const url = 'https://xyz.public.blob.vercel-storage.com/file.pdf';
const response = await fetch(url);
const blob = await response.blob();

Delete File:

import { del } from '@vercel/blob';

await del('https://xyz.public.blob.vercel-storage.com/file.jpg');

// Or delete multiple
await del([url1, url2, url3]);

Step 5: Streaming & Multipart

Streaming Upload:

import { put } from '@vercel/blob';
import { createReadStream } from 'fs';

const stream = createReadStream('./large-file.mp4');

const blob = await put('videos/large-file.mp4', stream, {
  access: 'public',
  contentType: 'video/mp4'
});

Multipart Upload (Large Files >500MB):

import { createMultipartUpload, uploadPart, completeMultipartUpload } from '@vercel/blob';

// 1. Start multipart upload
const upload = await createMultipartUpload('large-video.mp4', {
  access: 'public'
});

// 2. Upload parts (chunks)
const partSize = 100 * 1024 * 1024; // 100MB chunks
const parts = [];

for (let i = 0; i < totalParts; i++) {
  const chunk = getChunk(i, partSize);
  const part = await uploadPart(chunk, {
    uploadId: upload.uploadId,
    partNumber: i + 1
  });
  parts.push(part);
}

// 3. Complete upload
const blob = await completeMultipartUpload({
  uploadId: upload.uploadId,
  parts
});

Critical Rules

Always Do

✅ Use client upload tokens for client-side uploads - Never expose BLOB_READ_WRITE_TOKEN to client

✅ Set correct access level - public (CDN) or private (authenticated access)

✅ Validate file types and sizes - Before upload, check MIME type and size

✅ Use pathname organization - avatars/, uploads/, documents/ for structure

✅ Handle upload errors - Network failures, size limits, token expiration

✅ Clean up old files - Delete unused files to manage storage costs

✅ Set content-type explicitly - For correct browser handling (videos, PDFs)

Never Do

❌ Never expose BLOB_READ_WRITE_TOKEN to client - Use handleUpload() for client uploads

❌ Never skip file validation - Always validate type, size, content before upload

❌ Never upload files >500MB without multipart - Use multipart upload for large files

❌ Never use generic filenames - file.jpg collides, use ${timestamp}-${name} or UUID

❌ Never assume uploads succeed - Always handle errors (network, quota, etc.)

❌ Never store sensitive data unencrypted - Encrypt before upload if needed

❌ Never forget to delete temporary files - Old uploads consume quota


Known Issues Prevention

This skill prevents 10 documented issues:

Issue #1: Missing Environment Variable

Error: Error: BLOB_READ_WRITE_TOKEN is not defined Source: https://vercel.com/docs/storage/vercel-blob Why It Happens: Token not set in environment Prevention: Run vercel env pull .env.local and ensure .env.local in .gitignore.

Issue #2: Client Upload Token Exposed

Error: Security vulnerability, unauthorized uploads Source: https://vercel.com/docs/storage/vercel-blob/client-upload Why It Happens: Using BLOB_READ_WRITE_TOKEN directly in client code Prevention: Use handleUpload() to generate client-specific tokens with constraints.

Issue #3: File Size Limit Exceeded

Error: Error: File size exceeds limit (500MB) Source: https://vercel.com/docs/storage/vercel-blob/limits Why It Happens: Uploading file >500MB without multipart upload Prevention: Validate file size before upload, use multipart upload for large files.

Issue #4: Wrong Content-Type

Error: Browser downloads file instead of displaying (e.g., PDF opens as text) Source: Production debugging Why It Happens: Not setting contentType option, Blob guesses incorrectly Prevention: Always set contentType: file.type or explicit MIME type.

Issue #5: Public File Not Cached

Error: Slow file delivery, high egress costs Source: Vercel Blob best practices Why It Happens: Using access: 'private' for files that should be public Prevention: Use access: 'public' for publicly accessible files (CDN caching).

Issue #6: List Pagination Not Handled

Error: Only first 1000 files returned, missing files Source: https://vercel.com/docs/storage/vercel-blob/using-blob-sdk#list Why It Happens: Not iterating with cursor for large file lists Prevention: Use cursor-based pagination in loop until cursor is undefined.

Issue #7: Delete Fails Silently

Error: Files not deleted, storage quota fills up Source: https://github.com/vercel/storage/issues/150 Why It Happens: Using wrong URL format, blob not found Prevention: Use full blob URL from put() response, check deletion result.

Issue #8: Upload Timeout (Large Files)

Error: Error: Request timeout for files >100MB Source: Vercel function timeout limits Why It Happens: Serverless function timeout (10s free tier, 60s pro) Prevention: Use client-side upload with handleUpload() for large files.

Issue #9: Filename Collisions

Error: Files overwritten, data loss Source: Production debugging Why It Happens: Using same filename for multiple uploads Prevention: Add timestamp/UUID: `uploads/${Date.now()}-${file.name}` or addRandomSuffix: true.

Issue #10: Missing Upload Callback

Error: Upload completes but app state not updated Source: https://vercel.com/docs/storage/vercel-blob/client-upload#callback-after-upload Why It Happens: Not implementing onUploadCompleted callback Prevention: Use onUploadCompleted in handleUpload() to update database/state.


Configuration Files Reference

package.json

{
  "dependencies": {
    "@vercel/blob": "^2.0.0"
  }
}

.env.local

BLOB_READ_WRITE_TOKEN="vercel_blob_rw_xxxxx"

Common Patterns

Pattern 1: Avatar Upload

'use server';

import { put, del } from '@vercel/blob';

export async function updateAvatar(userId: string, formData: FormData) {
  const file = formData.get('avatar') as File;

  // Validate
  if (!file.type.startsWith('image/')) {
    throw new Error('Only images allowed');
  }

  // Delete old avatar
  const user = await db.query.users.findFirst({ where: eq(users.id, userId) });
  if (user?.avatarUrl) {
    await del(user.avatarUrl);
  }

  // Upload new
  const blob = await put(`avatars/${userId}.jpg`, file, {
    access: 'public',
    contentType: file.type
  });

  // Update database
  await db.update(users).set({ avatarUrl: blob.url }).where(eq(users.id, userId));

  return blob.url;
}

Pattern 2: Protected File Upload

'use server';

import { put } from '@vercel/blob';
import { auth } from '@/lib/auth';

export async function uploadDocument(formData: FormData) {
  const session = await auth();
  if (!session) throw new Error('Unauthorized');

  const file = formData.get('document') as File;

  // Upload as private
  const blob = await put(`documents/${session.user.id}/${file.name}`, file, {
    access: 'private' // Requires authentication to access
  });

  // Store in database with user reference
  await db.insert(documents).values({
    userId: session.user.id,
    url: blob.url,
    filename: file.name,
    size: file.size
  });

  return blob;
}

Pattern 3: Image Gallery with Pagination

import { list } from '@vercel/blob';

export async function getGalleryImages(cursor?: string) {
  const { blobs, cursor: nextCursor } = await list({
    prefix: 'gallery/',
    limit: 20,
    cursor
  });

  const images = blobs.map(blob => ({
    url: blob.url,
    uploadedAt: blob.uploadedAt,
    size: blob.size
  }));

  return { images, nextCursor };
}

Dependencies

Required:

  • @vercel/blob@^2.0.0 - Vercel Blob SDK

Optional:

  • sharp@^0.33.0 - Image processing before upload
  • zod@^3.24.0 - File validation schemas

Official Documentation


Package Versions (Verified 2025-10-29)

{
  "dependencies": {
    "@vercel/blob": "^2.0.0"
  }
}

Production Example

  • E-commerce: Product images, user uploads (500K+ files)
  • Blog Platform: Featured images, author avatars
  • SaaS: Document uploads, PDF generation, CSV exports
  • Errors: 0 (all 10 known issues prevented)

Troubleshooting

Problem: BLOB_READ_WRITE_TOKEN is not defined

Solution: Run vercel env pull .env.local, ensure .env.local in .gitignore.

Problem: File size exceeded (>500MB)

Solution: Use multipart upload with createMultipartUpload() API.

Problem: Client upload fails with token error

Solution: Ensure using handleUpload() server-side, don't expose read/write token to client.

Problem: Files not deleting

Solution: Use exact URL from put() response, check del() return value.


Complete Setup Checklist

  • Blob store created in Vercel dashboard
  • BLOB_READ_WRITE_TOKEN environment variable set
  • @vercel/blob package installed
  • File validation implemented (type, size)
  • Client upload uses handleUpload() (not direct token)
  • Content-type set for uploads
  • Access level correct (public vs private)
  • Deletion of old files implemented
  • List pagination handles cursor
  • Tested file upload/download/delete locally and in production

Questions? Issues?

  1. Check official docs: https://vercel.com/docs/storage/vercel-blob
  2. Verify environment variables are set
  3. Ensure using client upload tokens for client-side uploads
  4. Monitor storage usage in Vercel dashboard

FAQ

What does the vercel-blob skill do?

Integrate Vercel Blob object storage for file uploads, image management, and CDN-delivered assets in Next.js applications. Supports client-side uploads with presigned URLs and multipart transfers. Use when implementing file uploads (images, PDFs, videos), managing user-generated content, or troubleshooting missing tokens, size limit errors, or client upload failures.

How do I install vercel-blob?

Run npx skills add https://github.com/ovachiever/droid-tings --skill vercel-blob in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/vercel-blob/ (personal) or .claude/skills/vercel-blob/ (project).

Which AI agents can use vercel-blob?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is vercel-blob safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains vercel-blob?

It is published by ovachiever in ovachiever/droid-tings. It has 63 recorded installs.

More from ovachiever/droid-tings

View all →

security-auditor

ovachiever/droid-tings

Continuous security vulnerability scanning for OWASP Top 10, common vulnerabilities, and insecure patterns. Use when reviewing code, before deployments, or on file changes. Scans for SQL injection, XSS, secrets exposure, auth issues. Triggers on file changes, security mentions, deployment prep.

Security780 installs

react-hook-form-zod

ovachiever/droid-tings

Build type-safe validated forms in React using React Hook Form and Zod schema validation. Single schema works on both client and server for DRY validation with full TypeScript type inference via z.infer. Use when: building forms with validation, integrating shadcn/ui Form components, implementing multi-step wizards, handling dynamic field arrays with useFieldArray, or fixing uncontrolled to controlled warnings, resolver errors, async validation issues.

Frontend & Design722 installs

deep-reading-analyst

ovachiever/droid-tings

Open the skill to read its SKILL.md instructions.

281 installs

nextjs-shadcn-builder

ovachiever/droid-tings

Open the skill to read its SKILL.md instructions.

Frontend & Design262 installs

playwright browser automation

ovachiever/droid-tings

Complete browser automation with Playwright. Auto-detects dev servers, writes clean test scripts to /tmp. Test pages, fill forms, take screenshots, check responsive design, validate UX, test login flows, check links, automate any browser task. Use when user wants to test websites, automate browser interactions, validate web functionality, or perform any browser-based testing.

reportlab

ovachiever/droid-tings

Open the skill to read its SKILL.md instructions.

136 installs

Popular devops & cloud skills

View category →

microsoft-foundry

microsoft/azure-skills

Build, deploy, evaluate, optimize, fine-tune, and manage Microsoft Foundry agents, models, and resources end to end. USE FOR: foundry, azd ai agent, azd provision/deploy, hosted agent scaffold/develop/run/deploy/troubleshoot, prompt agent create, managed harness agent create, GitHub Copilot harness agent, create agent, update agent, add tool to agent, invoke agent, agent insights, evaluate agent, batch eval, continuous eval, continuous monitoring, agent CI/CD, optimize prompt, improve prompt, optimize agent instructions, Agent Optimizer scaffold, dataset curation from traces, deploy model, model fine-tuning (SFT/DPO/RFT), Foundry project, RBAC, role assignment, permissions, quota, capacity, region, deployment failure, AI Services, create Foundry resource, knowledge index, customize deployment, onboard, availability, training-data, grader, distillation, large file upload. DO NOT USE FOR: Azure Functions, App Service, general Azure deploy (use azure-deploy), general Azure prep (use azure

OfficialDevOps & Cloud579.1K installs

azure-diagnostics

microsoft/azure-skills

Debug Azure production issues on Azure using AppLens, Azure Monitor, resource health, and safe triage. WHEN: debug production issues, troubleshoot app service, app service high CPU, app service deployment failure, troubleshoot container apps, container app 503 or target port mismatch, troubleshoot functions, function telemetry missing or stopped after a config/connection-string change, troubleshoot AKS, VM RDP, Linux SSH, VM black screen, can't connect to VM, reset VM password, NSG or firewall blocking, kubectl cannot connect, kube-system/CoreDNS failures, pod pending, crashloop, node not ready, upgrade failures, analyze logs, KQL, insights, image pull failures, cold start issues, health probe failures, resource health, root cause of errors, troubleshoot event hubs, troubleshoot service bus, messaging SDK error, AMQP connection failure, message lock lost, lock renewal during long-running processing, service bus dead letter.

OfficialDevOps & Cloud569.8K installs

azure-prepare

microsoft/azure-skills

Prepare azd-based Azure projects for deployment: generates azure.yaml, infrastructure (Bicep/Terraform), and Dockerfiles for the Azure Developer CLI (azd) workflow. USE ONLY when the user explicitly wants to use azd as the deployment tool, or the project already has an azure.yaml file. DO NOT USE FOR: non-azd deployments, Python App Service code-only deploys (use python-appservice-deploy), or cross-cloud migration (use azure-cloud-migrate). WHEN: prepare app for azd, create azure.yaml, set up azd infrastructure, modernize app for Azure with azd, deploy with azd, function app, timer trigger, service bus trigger, event-driven function, managed identity, generate Bicep, generate Terraform, create and deploy to Azure.

OfficialDevOps & Cloud569K installs

azure-ai

microsoft/azure-skills

Use for Azure AI: Search, Speech, OpenAI, Document Intelligence. Helps with search, vector/hybrid search, speech-to-text, text-to-speech, transcription, OCR. WHEN: AI Search, query search, vector search, hybrid search, semantic search, speech-to-text, text-to-speech, transcribe, OCR, convert text to speech.

OfficialDevOps & Cloud568.8K installs

azure-deploy

microsoft/azure-skills

Execute Azure deployments for ALREADY-PREPARED applications that have existing .azure/deployment-plan.md and infrastructure files. DO NOT use this skill when the user asks to CREATE a new application — use azure-prepare instead. This skill runs azd up, azd deploy, terraform apply, and az deployment commands with built-in error recovery. Requires .azure/deployment-plan.md from azure-prepare and validated status from azure-validate. WHEN: "run azd up", "run azd deploy", "execute deployment", "push to production", "push to cloud", "go live", "ship it", "bicep deploy", "terraform apply", "publish to Azure", "launch on Azure". DO NOT USE WHEN: "create and deploy", "build and deploy", "create a new app", "set up infrastructure", "create and deploy to Azure using Terraform" — use azure-prepare for these.

OfficialDevOps & Cloud568.6K installs

azure-validate

microsoft/azure-skills

Pre-deployment validation for Azure readiness. Run deep checks on configuration, infrastructure (Bicep or Terraform), RBAC role assignments, managed identity permissions, and prerequisites before deploying. WHEN: validate my app, check deployment readiness, run preflight checks, verify configuration, check if ready to deploy, validate azure.yaml, validate Bicep, test before deploying, troubleshoot deployment errors, validate Azure Functions, validate function app, validate serverless deployment, verify RBAC roles, check role assignments, review managed identity permissions, what-if analysis, validate Container Apps deployment.

OfficialDevOps & Cloud568K installs