The smallest adapter in the set.
id:vercel-blob- Capabilities:
{ directUrl: false, contentTypeMetadata: true, streamingPut: true }
Every blob is world-readable
Vercel Blob has one access mode: public. Its URL is permanent and
unauthenticated. The CMS never hands it out (the API returns its own
/raw route, and checks membership), but anyone who obtains the blob URL can
fetch the asset forever. Fine for marketing images. Not for confidential
media.
Install
npm install @orthacms/media-provider-vercel-blobConfigure
# .env — only needed OFF Vercel; on Vercel the SDK reads it itself
BLOB_READ_WRITE_TOKEN=vercel_blob_rw_...A generated app’s config/media-storage.ts (the reference server ships with
local disk):
import type { VercelBlobStorageConfig } from '@orthacms/media-provider-vercel-blob';
import { defined, readEnv } from '@orthacms/utils-server';
export function mediaStorage(): VercelBlobStorageConfig {
return defined({ token: readEnv('BLOB_READ_WRITE_TOKEN') });
}VercelBlobStorageConfig: token?, keyPrefix?, api?. api is
{ put, head, del } from @vercel/blob, replaceable for tests or a custom
transport.
Register
// apps/server/src/plugins.ts
import { createVercelBlobStorageProvider } from '@orthacms/media-provider-vercel-blob';
MediaServerPlugin({
provider: createVercelBlobStorageProvider(config.plugins.media.storage),
config: config.plugins.media
});Leave MEDIA_DIRECT_SERVE unset: signed-url is a boot error on this backend.
Verify
verify() asks the store about a path that does not exist. “Not found” proves
the token; anything else refuses the boot.
Upload a file (curl example). The blob appears in
the store at <workspaceId>/<assetId>/<file-name>. Downloads stream through the app
(200, never a redirect).
Why directUrl is false
The port’s directUrl promises a short lifetime and a pinned
Content-Disposition and Content-Type. A Vercel Blob URL has neither, so
declaring it would hand out an unrevocable link and serve an uploaded .html
inline. Proxying also keeps the membership check on every read.
Behaviour worth knowing
addRandomSuffix: false: the computed pathname is the storage key. With the SDK’s default suffix, later reads and removes would miss.- The upload and the pipeline are awaited together, so a failing upload cannot become an unhandled rejection that kills the process.