Connect Backblaze B2 to V0-generated Next.js apps using the B2 native API with application keys for authentication. Install axios or use Node.js fetch to call B2's REST API endpoints in Next.js API routes — authorize your account, get an upload URL, and upload or download files. Store your B2 Application Key ID and Application Key as Vercel environment variables for secure server-side access.
Using Backblaze B2 Native API for File Storage in V0-Generated Next.js Apps
Backblaze B2 is one of the most cost-effective object storage solutions available, at $0.006/GB per month compared to AWS S3's $0.023/GB. For V0-generated applications that need to store user-uploaded files, assets, backups, or media at scale, B2 offers significant cost savings while maintaining reliable performance and a clean API.
Backblaze B2 offers two API interfaces: the native B2 API and an S3-compatible API endpoint. The native B2 API (covered on this page) uses B2's own authentication model and endpoint structure. The S3-compatible API (covered on the backblaze-b2-cloud-storage page) uses the same AWS SDK and request signing as Amazon S3 — useful if you have existing S3 code you want to reuse. The native API is worth understanding because B2's authorization token caching can improve performance for high-frequency upload applications.
The B2 native API authentication is a three-step process unique to Backblaze: first call `b2_authorize_account` with your key credentials to get an authorization token and the API URL for your account, then call `b2_get_upload_url` with the authorization token to get a specific upload endpoint URL and upload authorization token, and finally call the upload endpoint with the file content. This design allows B2 to distribute upload traffic across infrastructure while maintaining a consistent authorization system.
Integration method
Backblaze B2 native API integration requires a multi-step authentication process: first authorize your account with your Application Key ID and Application Key to get an authorization token and API URL, then use that token to get an upload URL, and finally upload the file. All B2 API calls happen in Next.js API routes using your credentials stored as Vercel environment variables. The native B2 API is slightly different from the S3-compatible endpoint — this page covers the native API specifically.
Prerequisites
- A Backblaze B2 account — sign up at backblaze.com (free tier includes 10GB storage)
- A B2 bucket created in your Backblaze account (set to private or public depending on your use case)
- A B2 Application Key with Read and Write permissions for your bucket — create at Backblaze B2 → App Keys → Add a New Application Key
- Your Application Key ID and Application Key (secret) from the App Keys page
- A V0 account at v0.dev and a Vercel account for deployment
Step-by-step guide
Generate the File Upload UI with V0
Generate the File Upload UI with V0
Start by generating the file upload interface in V0 before working on the B2 API integration. Having the UI ready first lets you test the upload flow visually with mock data before connecting real B2 API calls. In V0's chat, describe the specific upload experience you want. For most applications, this includes a drag-and-drop upload zone that accepts specific file types, a file list showing queued uploads with name, size, and a remove button, upload progress indicators for each file during the upload process, and a completion state showing the uploaded file with its download URL or a link to view it. Ask V0 to generate the upload component as a client component (`'use client'`) since it handles browser file selection events, drag-and-drop interactions, and progress updates. The component should accept an `onUpload` callback prop that receives the FormData to send to your API route — this keeps the API call outside the UI component for testability. Also ask V0 to generate the file list display component that shows uploaded files with their B2 download URLs. Include a copy-to-clipboard button for the download URL, a delete button (which will call a delete API route), and a preview capability for images (using Next.js Image with the B2 URL as the source).
Build a file upload component with a large drag-and-drop zone (dashed border, cloud upload icon, 'Drop files here or click to browse' text). Below it, show a list of files being uploaded with a progress bar showing 0-100%, file name, file size, and a cancel button. Completed uploads show a green checkmark and a copy URL button. Failed uploads show red with an error message and retry button. Support multiple simultaneous uploads.
Paste this in V0 chat
Pro tip: Ask V0 to generate a useFileUpload custom hook that manages the upload state (pending, uploading, completed, error) for multiple concurrent file uploads. This hook handles the fetch call to your API route and updates the UI state based on responses.
Expected result: A file upload UI renders in V0's sandbox with drag-and-drop support, simulated progress bars, and a completed state with placeholder URLs. The component architecture is ready to connect to real B2 API routes.
Understand the B2 Native API Authentication Flow
Understand the B2 Native API Authentication Flow
The B2 native API uses a three-step authentication model that is unique to Backblaze. Understanding this flow before coding makes the implementation clearer and helps debug issues when they occur. Step 1 is Account Authorization. Call `GET https://api.backblaze.com/b2api/v2/b2_authorize_account` with HTTP Basic authentication using your Application Key ID as the username and Application Key as the password. The response contains the `authorizationToken` (valid for 24 hours), the `apiUrl` (the base URL for all subsequent API calls, varies by account), and the `downloadUrl` (for downloading files). Step 2 is Get Upload URL. Using the `apiUrl` and `authorizationToken` from Step 1, call `POST {apiUrl}/b2api/v2/b2_get_upload_url` with the `bucketId` in the request body. The response gives you an `uploadUrl` (a specific endpoint for this upload) and an `uploadAuthToken` (a one-time token for this specific upload). This upload URL and token are single-use — you need to get a new one for each file. Step 3 is Upload File. Use the `uploadUrl` from Step 2 to `POST` the file content with headers including the `uploadAuthToken`, the filename (`X-Bz-File-Name`), file content type (`Content-Type`), SHA1 hash of the file content (`X-Bz-Content-Sha1`) for integrity verification, and optionally custom file metadata prefixed with `X-Bz-Info-`. Cache the Step 1 token in your API route (module-level variable) to avoid re-authorizing for every upload. The authorization token is valid for 24 hours and can be reused across multiple upload operations, saving an API round trip per upload.
Add an API health status badge to the file manager page showing 'B2 Storage Online' with a green dot if the authorization check passes, or 'B2 Storage Unavailable - uploads paused' with a red dot if the /api/b2/status endpoint returns an error. Check status on page load and every 5 minutes.
Paste this in V0 chat
1// lib/b2.ts — B2 authorization token management2let cachedAuth: { authorizationToken: string; apiUrl: string; downloadUrl: string; expiresAt: number } | null = null34export async function getB2Authorization() {5 // Return cached auth if still valid (expires 1 hour before actual expiry to be safe)6 if (cachedAuth && cachedAuth.expiresAt > Date.now()) {7 return cachedAuth8 }910 const keyId = process.env.B2_APPLICATION_KEY_ID!11 const applicationKey = process.env.B2_APPLICATION_KEY!1213 const credentials = Buffer.from(`${keyId}:${applicationKey}`).toString('base64')1415 const response = await fetch('https://api.backblaze.com/b2api/v2/b2_authorize_account', {16 headers: { Authorization: `Basic ${credentials}` },17 })1819 if (!response.ok) {20 const error = await response.json()21 throw new Error(`B2 authorization failed: ${error.message}`)22 }2324 const auth = await response.json()25 cachedAuth = {26 authorizationToken: auth.authorizationToken,27 apiUrl: auth.apiUrl,28 downloadUrl: auth.downloadUrl,29 expiresAt: Date.now() + (23 * 60 * 60 * 1000), // Cache for 23 hours30 }31 return cachedAuth32}Pro tip: Module-level caching of the B2 authorization token works in Vercel serverless functions because warm function instances reuse the module scope. On cold starts, a new authorization call is made automatically. This pattern avoids unnecessary re-authorization while still refreshing expired tokens.
Expected result: The B2 authorization utility is created and tested. Calling getB2Authorization() returns a valid authorizationToken and apiUrl, confirming your B2 credentials work correctly.
Create the B2 Upload and Download API Routes
Create the B2 Upload and Download API Routes
With the B2 authorization utility in place, create the Next.js API routes for file upload and download. These routes handle the complete B2 native API flow — authorization, getting an upload URL, and executing the upload. Create the upload route at `app/api/b2/upload/route.ts`. This route accepts a POST request with a file in FormData, uses the authorization utility to get a token, calls `b2_get_upload_url` with your bucket ID to get the upload URL and one-time token, computes the SHA1 hash of the file content for B2's integrity verification, and uploads the file with all required headers. Return the uploaded file's `fileId`, `fileName`, and the download URL in the response. Create the download URL generation route at `app/api/b2/download-url/route.ts`. For private buckets, files require a download authorization. This route calls `b2_get_download_authorization` with the file path and a validity period (up to 7 days) to get a pre-authorized download URL. Return this URL to the client. For public buckets, download URLs are simply `{downloadUrl}/file/{bucketName}/{fileName}` — no authorization needed. Create a file list route at `app/api/b2/files/route.ts` that calls `b2_list_file_names` to list files in your bucket. This is useful for building file manager interfaces. Create a delete route at `app/api/b2/delete/route.ts` that accepts a file ID and file name, then calls `b2_delete_file_version` to permanently delete a file from B2.
Wire up the upload component to POST FormData to /api/b2/upload. When the upload completes, show the B2 download URL in a read-only input field with a copy button, and add the uploaded file to a list below with its name, size, upload date, and download URL.
Paste this in V0 chat
1// app/api/b2/upload/route.ts2import { NextRequest, NextResponse } from 'next/server'3import crypto from 'crypto'4import { getB2Authorization } from '@/lib/b2'56export async function POST(request: NextRequest) {7 try {8 const formData = await request.formData()9 const file = formData.get('file') as File10 if (!file) {11 return NextResponse.json({ error: 'No file provided' }, { status: 400 })12 }1314 const bucketId = process.env.B2_BUCKET_ID!15 const auth = await getB2Authorization()1617 // Step 2: Get upload URL18 const uploadUrlResponse = await fetch(`${auth.apiUrl}/b2api/v2/b2_get_upload_url`, {19 method: 'POST',20 headers: {21 Authorization: auth.authorizationToken,22 'Content-Type': 'application/json',23 },24 body: JSON.stringify({ bucketId }),25 })2627 if (!uploadUrlResponse.ok) {28 throw new Error('Failed to get B2 upload URL')29 }3031 const { uploadUrl, authorizationToken: uploadAuthToken } = await uploadUrlResponse.json()3233 // Step 3: Upload the file34 const fileBuffer = await file.arrayBuffer()35 const fileBytes = new Uint8Array(fileBuffer)36 const sha1Hash = crypto.createHash('sha1').update(fileBytes).digest('hex')3738 // Encode filename for URL safety39 const fileName = encodeURIComponent(file.name)4041 const uploadResponse = await fetch(uploadUrl, {42 method: 'POST',43 headers: {44 Authorization: uploadAuthToken,45 'X-Bz-File-Name': fileName,46 'Content-Type': file.type || 'application/octet-stream',47 'Content-Length': file.size.toString(),48 'X-Bz-Content-Sha1': sha1Hash,49 },50 body: fileBytes,51 })5253 if (!uploadResponse.ok) {54 const error = await uploadResponse.json()55 throw new Error(`Upload failed: ${error.message}`)56 }5758 const uploadedFile = await uploadResponse.json()59 const downloadUrl = `${auth.downloadUrl}/file/${process.env.B2_BUCKET_NAME}/${file.name}`6061 return NextResponse.json({62 fileId: uploadedFile.fileId,63 fileName: uploadedFile.fileName,64 downloadUrl,65 size: uploadedFile.contentLength,66 })67 } catch (error) {68 console.error('B2 upload error:', error)69 return NextResponse.json({ error: 'Upload failed' }, { status: 500 })70 }71}7273// app/api/b2/download-url/route.ts — Generate private download URL74export async function POST(request: NextRequest) {75 try {76 const { fileName, validDurationSeconds = 3600 } = await request.json()77 const bucketId = process.env.B2_BUCKET_ID!78 const auth = await getB2Authorization()7980 const response = await fetch(`${auth.apiUrl}/b2api/v2/b2_get_download_authorization`, {81 method: 'POST',82 headers: {83 Authorization: auth.authorizationToken,84 'Content-Type': 'application/json',85 },86 body: JSON.stringify({87 bucketId,88 fileNamePrefix: fileName,89 validDurationInSeconds: validDurationSeconds,90 }),91 })9293 const { authorizationToken } = await response.json()94 const downloadUrl = `${auth.downloadUrl}/file/${process.env.B2_BUCKET_NAME}/${encodeURIComponent(fileName)}?Authorization=${authorizationToken}`9596 return NextResponse.json({ downloadUrl })97 } catch (error) {98 return NextResponse.json({ error: 'Failed to generate download URL' }, { status: 500 })99 }100}Pro tip: B2 enforces a maximum of 10 concurrent uploads per bucket, and upload URLs expire after 24 hours or when an error occurs during upload. Always catch upload errors and get a fresh upload URL when retrying — do not reuse a failed upload URL.
Expected result: The /api/b2/upload route accepts file uploads and stores them in your B2 bucket. The response includes the file ID, file name, and download URL. Files appear in your Backblaze B2 bucket dashboard after upload.
Configure Vercel Environment Variables and Deploy
Configure Vercel Environment Variables and Deploy
Add your Backblaze B2 credentials to Vercel. Go to Vercel Dashboard → your project → Settings → Environment Variables. Add `B2_APPLICATION_KEY_ID` with your Application Key ID from Backblaze B2. This is the public identifier for your API key (distinct from the key itself). Add `B2_APPLICATION_KEY` with your Application Key — the secret part of the API credential. This must never have the `NEXT_PUBLIC_` prefix. Add `B2_BUCKET_ID` with your bucket's ID (found in the Backblaze B2 bucket list, click on your bucket to see the details). Add `B2_BUCKET_NAME` with your bucket's name (used to construct download URLs). To find your Application Key ID and Application Key: in Backblaze B2, go to App Keys → Add a New Application Key. Give it a name, select your bucket under 'Allow access to Bucket(s)', choose 'Read and Write' for file operations, and click Create New Key. Copy both the keyID and the applicationKey — the applicationKey is only shown once. For public buckets where all uploaded files should be publicly accessible, set your bucket's File Access to 'Public' in the B2 dashboard. Download URLs then follow the pattern `https://f003.backblazeb2.com/file/{bucketName}/{fileName}` (the f003 domain varies by your account region). For private buckets, use the `b2_get_download_authorization` route to generate expiring pre-signed URLs. After deployment, test by uploading a small test file through your application. Verify the file appears in your B2 bucket dashboard and the download URL works correctly. Check the download URL format — for B2 CDN, the URL uses the `f{region}.backblazeb2.com` domain format.
Add a storage quota display to the file manager showing current storage used vs available in a horizontal progress bar. Show the values formatted as '2.4 GB used of 10 GB' with the percentage and bar. Fetch this from /api/b2/storage-info on page load.
Paste this in V0 chat
1// Vercel Dashboard → Settings → Environment Variables:2// B2_APPLICATION_KEY_ID = your_key_id_from_backblaze3// B2_APPLICATION_KEY = your_application_key_secret4// B2_BUCKET_ID = your_bucket_id_from_backblaze_dashboard5// B2_BUCKET_NAME = your-bucket-name67// next.config.ts — allow B2 image domains8const nextConfig = {9 images: {10 remotePatterns: [11 {12 protocol: 'https',13 hostname: '**.backblazeb2.com',14 },15 ],16 },17}18export default nextConfig1920// B2 bucket access types:21// Public: download URL is https://f{region}.backblazeb2.com/file/{bucketName}/{fileName}22// Private: need download authorization token appended as ?Authorization=TOKENPro tip: Connect Backblaze B2 to Cloudflare CDN for free egress bandwidth. Backblaze B2 and Cloudflare are Bandwidth Alliance partners — B2 bandwidth used through Cloudflare's CDN is free, dramatically reducing costs for apps serving files to users globally.
Expected result: Files upload successfully to B2 from your deployed Vercel application. Downloaded files serve correctly via the B2 download URL. The B2 application key, bucket ID, and bucket name are all configured as Vercel environment variables.
Common use cases
User Profile Image Upload
A V0-generated app lets users upload profile photos. The Next.js API route receives the uploaded file from the browser, authorizes with B2, gets an upload URL, uploads the image to a B2 bucket, and returns the public download URL to store in the user's database record.
Build a profile settings page with an avatar upload section. Show the current profile photo in a circle (96px), a camera icon overlay on hover, and a hidden file input that opens when the circle is clicked. After selecting an image, show a preview with crop handles for 1:1 ratio, a Save and Cancel button. Display an upload progress bar while saving.
Copy this prompt to try it in V0
Document Management System
A project management app stores PDFs, spreadsheets, and presentations in B2 and displays them in a V0-generated document library. Users can upload new documents, download existing ones, and share time-limited download links with external collaborators.
Create a document library page with a folder tree sidebar and a main area showing a filterable document grid with file type icon, name, size, upload date, and uploaded by columns. Include a drag-and-drop upload zone at the top of the main area, a search bar, and action buttons per file: Download, Copy Share Link, and Delete. Group files by date uploaded with date headers.
Copy this prompt to try it in V0
Media Asset Storage for Content Platform
A content creation platform stores video thumbnails, episode audio files, and podcast artwork in B2 for low-cost storage of large media files. The API route handles chunked upload for large files (100MB+) and returns CDN URLs for fast global delivery.
Build a media upload component that accepts video files up to 2GB with a multi-step upload UI: file selection with drag-drop, metadata form (title, description, tags), upload progress with percentage and estimated time remaining, and completion with the shareable CDN URL. Show a thumbnail preview extracted from the video file after selection.
Copy this prompt to try it in V0
Troubleshooting
401 Unauthorized from b2_authorize_account despite correct credentials
Cause: The Application Key ID or Application Key is incorrect, the key has been deleted or suspended, or the key was created for a different B2 account than the one you are trying to authorize.
Solution: Go to Backblaze B2 → App Keys and verify your key exists and is not suspended. Delete and recreate the key if needed, and copy both the keyID and applicationKey carefully. Verify the NEXT_PUBLIC_ prefix is NOT on B2_APPLICATION_KEY — it must be server-only.
Upload fails with 'File name cannot start with a /' error
Cause: The filename passed as X-Bz-File-Name header starts with a forward slash, which B2 does not allow. This often happens when using a path separator or when the file input provides a path like /uploads/image.png instead of just image.png.
Solution: Strip leading slashes and path components from the filename before uploading. Use file.name (from the File API) directly without modification, as browser File objects return just the filename without path.
1// Strip path components from filename2const cleanFileName = file.name.replace(/^.*[\\/]/, '') // Remove path3const encodedFileName = encodeURIComponent(cleanFileName)4// Use encodedFileName in X-Bz-File-Name headerSHA1 hash mismatch error during upload
Cause: The SHA1 hash computed before upload does not match the hash B2 computes of the received data. This can happen if the file content changes between hash computation and upload, or if the data is being re-encoded (e.g., text files with line ending conversion).
Solution: Compute the SHA1 hash from the same ArrayBuffer you send as the request body. Ensure you use new Uint8Array(fileBuffer) for both the hash computation and the request body to prevent data transformation.
1const fileBuffer = await file.arrayBuffer()2const fileBytes = new Uint8Array(fileBuffer)3// Hash the SAME bytes you will upload4const sha1Hash = crypto.createHash('sha1').update(fileBytes).digest('hex')5// Upload the SAME bytes6body: fileBytes // Not fileBuffer or another copyBest practices
- Cache the B2 authorization token (valid 24 hours) in module scope to avoid authorizing before every upload — each b2_authorize_account call counts toward B2's API call limits.
- Always compute and send the SHA1 hash in the X-Bz-Content-Sha1 header — this ensures B2 verifies data integrity and catches transmission errors.
- Use the Backblaze B2 and Cloudflare partnership for free egress — configure Cloudflare as a CDN in front of your B2 bucket to eliminate bandwidth costs for file downloads.
- For files over 100MB, use B2's large file upload API (b2_start_large_file, b2_get_upload_part_url, b2_upload_part, b2_finish_large_file) which uploads in 100MB chunks and supports resume on failure.
- Store B2_APPLICATION_KEY as a server-only Vercel environment variable (no NEXT_PUBLIC_ prefix) — this key grants full access to your B2 bucket and must never reach browser code.
- Create application keys with the minimum required permissions — if your app only needs to upload to one bucket, restrict the key to that specific bucket and to write operations only.
- Implement retry logic with a fresh upload URL when uploads fail — B2 upload URLs are single-use and cannot be reused after an error.
Alternatives
Using B2's S3-compatible endpoint with the AWS SDK allows reuse of existing S3 integration code and compatibility with the broader S3 ecosystem, at the cost of slightly less optimal performance than the native API.
OneDrive via Microsoft Graph is better for applications where users need to access their own personal file storage rather than a centralized application storage bucket.
Frequently asked questions
What is the difference between Backblaze B2 native API and the S3-compatible API?
The native B2 API uses Backblaze's own authentication and endpoint format (b2_authorize_account, b2_get_upload_url, etc.) and is slightly more efficient for pure B2 workflows. The S3-compatible API uses the same authentication and endpoint structure as Amazon S3, allowing you to use the AWS SDK or any S3-compatible library. If you have existing S3 code or are using the AWS SDK, use the S3-compatible endpoint. If you are building B2-first without S3 constraints, the native API is slightly simpler to reason about.
How much does Backblaze B2 cost compared to AWS S3?
Backblaze B2 charges $0.006/GB/month for storage and $0.01/GB for download bandwidth (free through Cloudflare CDN). AWS S3 charges $0.023/GB/month for storage and $0.09/GB for downloads. For the same 100GB of stored data with 1TB of monthly downloads, B2 costs approximately $0.60/month vs AWS S3's approximately $92/month with Cloudflare CDN applied.
Can Vercel serverless functions stream large files to B2?
Vercel serverless functions have a 4.5MB request and response body limit. For files larger than 4.5MB, you need to either use pre-signed B2 upload URLs (upload directly from browser to B2) or use a chunked upload approach with B2's large file API. For direct browser-to-B2 uploads, your API route generates a pre-authorized upload URL and returns it to the client, which then uploads directly to B2 without routing through Vercel.
How do I make uploaded B2 files publicly accessible?
Set your B2 bucket's File Access to 'Public' in the Backblaze B2 dashboard. Public buckets have predictable download URLs in the format https://f{region}.backblazeb2.com/file/{bucketName}/{fileName}. Replace {region} with your account's region number (e.g., f003), found in your B2 account dashboard. For private buckets, use b2_get_download_authorization to generate time-limited signed URLs.
Can V0 generate the chunked upload logic for large files?
Yes. Ask V0 to generate a large file upload component that uses B2's multipart upload API: b2_start_large_file to initiate, then multiple b2_upload_part calls for 100MB chunks, then b2_finish_large_file to complete. V0 can generate the client-side chunking logic and the corresponding Next.js API routes for each step of the multipart upload process.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation