NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
npm · #4649 most downloaded on npm
The Vercel Blob JavaScript API client
Last release 1 months ago
10 Aug 2026
Ships fairly regularly
a new release about every 2 weeks
Nearly every release is documented
notes for 59 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
3 years old
179 releases · first in 2023
One column per quarter.
…type always comes from the optimizer output. Deprecates the optimizeImage option on put and the putFromUrl function in favor of putImage; both keep wo…
putImage(pathname, bodyOrUrl, options): optimizes an image through Vercel Image Optimization and stores only the optimized output. The source can be the image content itself (string, File, Blob, Buffer or Stream) or a URL instance pointing at a public http(s) image, which is fetched server-side. Options mirror put (access, addRandomSuffix, allowOverwrite, cacheControlMaxAge, ifMatch) plus the required optimizeImage parameters (width, quality, format); contentType is not accepted since the stored content type always comes from the optimizer output. Deprecates the optimizeImage option on put and the putFromUrl function in favor of putImage; both keep working.Nothing published for this version
ad5a134: Add image optimization support: a new optimizeImage option on put and a new putFromUrl method. Both optimize the image through Vercel Image O
ad5a134: Add image optimization support: a new optimizeImage option on put and a new putFromUrl method. Both optimize the image through Vercel Image Optimization before storing it (only the optimized output is stored) and require OIDC authentication.
const result = await put("avatars/foo.webp", body, {
access: "public",
optimizeImage: { width: 128, quality: 75, format: "webp" },
});
const result = await putFromUrl(
"avatars/foo.webp",
"https://example.com/photo.jpg",
{
access: "public",
optimizeImage: { width: 128, quality: 75, format: "webp" },
}
);
Nothing published for this version
d0118c4: Add a useCache option to presignUrl() for get operations. When useCache: false, the presigned URL includes a cache=0 query param so fetches b
useCache option to presignUrl() for get operations. When useCache: false, the presigned URL includes a cache=0 query param so fetches bypass the CDN cache and read the latest content directly from origin storage. Like get(), the bypass only applies to private blobs. The param is not part of the signed payload, so holders of a presigned URL can also add or remove it manually.c4976ba: Add rename(fromUrlOrPathname, toPathname, options) to move a blob to another pathname. The blob is copied to the new pathname and the source
rename(fromUrlOrPathname, toPathname, options) to move a blob to another pathname. The blob is copied to the new pathname and the source is deleted afterwards; if the copy fails the source is left untouched. By default renaming onto an existing blob throws — pass allowOverwrite: true to replace it, or addRandomSuffix: true to generate a unique destination. Requires a read-write token (client tokens are not supported).useCache option on get(). Passing useCache: false bypasses the CDN cache and serves the blob directly from origin storage (via the cache=0 query parameter), guaranteeing the latest content at the cost of slower reads. Defaults to true.31a8b8f: Deprecate the useCache option on get(). The backend no longer honors the cache=0 query parameter it produced, so the option is now a no-op —…
useCache option on get(). The backend no longer honors the cache=0 query parameter it produced, so the option is now a no-op — reads always go through the standard caching path. The option is still accepted (and ignored) to avoid breaking existing callers, and will be removed in a future major version.@vercel/oidc's refreshing getVercelOidcToken instead of the non-refreshing getVercelOidcTokenSync. This refreshes an expired token in development environments. In production with a valid token, behavior is unchanged. If a refresh is needed but fails, the token is treated as absent so callers still fall back to BLOB_READ_WRITE_TOKEN.Nothing published for this version
b7027de: Read the Vercel OIDC token via the @vercel/oidc package (getVercelOidcTokenSync) instead of an inlined copy. This makes the dependency explic
@vercel/oidc package (getVercelOidcTokenSync) instead of an inlined copy. This makes the dependency explicit and discoverable, and matches how other Vercel packages consume OIDC. Behavior is unchanged except for one edge case: a blank x-vercel-oidc-token request-context header now resolves to no token rather than falling back to VERCEL_OIDC_TOKEN.Nothing published for this version
20eeaff: Add Vercel OIDC auth and presigned URLs
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
d2ea7cf: Enforce maximumSizeInBytes client-side for multipart uploads. Bodies with a known size (Blob, File, Buffer) are now checked before the upload
maximumSizeInBytes client-side for multipart uploads. Bodies with a known size (Blob, File, Buffer) are now checked before the upload starts, avoiding wasted API calls.createChunkTransformStream bypassing backpressure by removing incorrect queueMicrotask wrapping.Nothing published for this version
Nothing published for this version
c9d9a1a: Apply ifMatch/allowOverwrite validation to handleUpload and generateClientTokenFromReadWriteToken. When ifMatch is set via onBeforeGenerateTo
ifMatch/allowOverwrite validation to handleUpload and generateClientTokenFromReadWriteToken. When ifMatch is set via onBeforeGenerateToken or direct token generation, allowOverwrite is now implicitly enabled. Explicitly passing allowOverwrite: false with ifMatch throws a clear error.ifMatch imply allowOverwrite: true on put(). Previously, using ifMatch without explicitly setting allowOverwrite: true would cause the server to send conflicting conditional headers to S3, resulting in 500 errors. Now the SDK implicitly enables allowOverwrite when ifMatch is set, and throws a clear error if allowOverwrite: false is explicitly combined with ifMatch.a9a733a: fix: validate URL domain in get() to prevent sending the token to arbitrary hosts
get() to prevent sending the token to arbitrary hosts04ca1f0: Add private storage support (beta), a new get() method, and conditional gets
04ca1f0: Add private storage support (beta), a new get() method, and conditional gets
Private storage (beta)
You can now upload and read private blobs by setting access: 'private' on put() and get(). Private blobs require authentication to access — they are not publicly accessible via their URL.
New get() method
Fetch blob content by URL or pathname. Returns a ReadableStream along with blob metadata (url, pathname, contentType, size, etag, etc.).
Conditional gets with ifNoneMatch
Pass an ifNoneMatch option to get() with a previously received ETag. When the blob hasn't changed, the response returns statusCode: 304 with stream: null, avoiding unnecessary re-downloads.
Example
import { put, get } from "@vercel/blob";
// Upload a private blob
const blob = await put("user123/avatar.png", file, { access: "private" });
// Read it back
const response = await get(blob.pathname, { access: "private" });
// response.stream — ReadableStream of the blob content
// response.blob — metadata (url, pathname, contentType, size, etag, ...)
// Conditional get — skip download if unchanged
const cached = await get(blob.pathname, {
access: "private",
ifNoneMatch: response.blob.etag,
});
if (cached.statusCode === 304) {
// Blob hasn't changed, reuse previous data
}
Learn more: https://vercel.com/docs/vercel-blob/private-storage
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
2b1cbbc: Add ifMatch option to del() for conditional deletes (optimistic concurrency control). Only works for single-URL deletes.
ifMatch option to del() for conditional deletes (optimistic concurrency control). Only works for single-URL deletes.Nothing published for this version
6c68442: Add ETag support for conditional writes (optimistic concurrency control)
6c68442: Add ETag support for conditional writes (optimistic concurrency control)
etag in all blob responses (put, copy, head, list, multipart)ifMatch option in put/copy/createMultipartUpload for conditional writesBlobPreconditionFailedError for ETag mismatch (HTTP 412)When multiple users or processes might update the same blob concurrently, use ifMatch to ensure you don't overwrite someone else's changes:
import { put, head, BlobPreconditionFailedError } from "@vercel/blob";
// User 1: Read the current blob and get its ETag
const metadata = await head("config.json");
console.log(metadata.etag); // e.g., '"abc123"'
// User 2: Also reads the same blob (same ETag)
const metadata2 = await head("config.json");
// User 1: Updates the blob with ifMatch
// This succeeds because the ETag matches
const result1 = await put(
"config.json",
JSON.stringify({ setting: "user1" }),
{
access: "public",
allowOverwrite: true, // Required when updating existing blobs
ifMatch: metadata.etag, // Only write if ETag still matches
}
);
console.log(result1.etag); // New ETag: '"def456"'
// User 2: Tries to update with their (now stale) ETag
// This fails because User 1 already changed the blob
try {
await put("config.json", JSON.stringify({ setting: "user2" }), {
access: "public",
allowOverwrite: true,
ifMatch: metadata2.etag, // Stale ETag - blob was modified!
});
} catch (error) {
if (error instanceof BlobPreconditionFailedError) {
// The blob was modified since we last read it
// Re-fetch, merge changes, and retry
const freshMetadata = await head("config.json");
await put("config.json", JSON.stringify({ setting: "user2" }), {
access: "public",
allowOverwrite: true,
ifMatch: freshMetadata.etag, // Use fresh ETag
});
}
}
allowOverwrite: true: Required when updating an existing blob at the same pathifMatch: Only performs the write if the blob's current ETag matches this value"abc123")Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
e2de71a: Upgrade undici to fix security issue warning
To continue receiving onUploadCompleted callback once a file is uploaded with Client Uploads when not hosted on Vercel, you need to provide the callba
0b8ead9: BREAKING CHANGE:
To continue receiving onUploadCompleted callback once a file is uploaded with Client Uploads when not hosted on Vercel, you need to provide the callbackUrl at the onBeforeGenerateToken step when using handleUpload.
When hosted on Vercel:
No code changes required. The callbackUrl is inferred from Vercel system environment variables:
VERCEL_BRANCH_URL when available, otherwise VERCEL_URLVERCEL_PROJECT_PRODUCTION_URLIf you're not hosted on Vercel or you're not using Vercel system environment variables, your will need to provide the callbackUrl:
Before:
await handleUpload({
body,
request,
onBeforeGenerateToken: async (pathname) => {
/* options */
},
onUploadCompleted: async ({ blob, tokenPayload }) => {
/* code */
},
});
After:
await handleUpload({
body,
request,
onBeforeGenerateToken: async (pathname) => {
return { callbackUrl: 'https://example.com' }; // the path to call will be automatically computed
},
onUploadCompleted: async ({ blob, tokenPayload }) => {
/* code */
},
});
For local development:
Set the VERCEL_BLOB_CALLBACK_URL environment variable to your tunnel URL:
VERCEL_BLOB_CALLBACK_URL=https://abc123.ngrok-free.app
See the updated documentation at https://vercel.com/docs/vercel-blob/client-upload to know more.
Details:
Before this commit, during Client Uploads, we would infer the callbackUrl at the client side level (browser) based on location.href (for convenience).
This is wrong and allows browsers to redirect the onUploadCompleted callback to a different website.
While not a security risk, because the blob urls are already public and the browser knows them, it still pose a risk of database drift if you're relying on onUploadCompleted callback to update any system on your side.
d02e08a: Enable auto pipelining by default. We're making this a major release for safety, but we believe most applications can upgrade from 1.x to 2.x without any changes. Auto pipelining should work by default and improve performance.
BREAKING CHANGE: Auto pipelining is on by default now. See https://upstash.com/docs/oss/sdks/ts/redis/pipelining/auto-pipeline. This brings performance benefits to any code making multiple redis commands simultaneously.
If you detect bugs because of this, please open them at https://github.com/vercel/storage/issues.
You can disable this new behavior with:
import { createClient } from '@vercel/kv';
const kv = createClient({
url: ..,
token: ..,
enableAutoPipelining: false
});
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →