Beta . Ortha CMS is in public beta. Every plan is free until it ends.
Features Ortha CMS on GitHub

Vercel Blob

Media plugin @orthacms/media-server@orthacms/media-admin

Smallest setup on Vercel — and the one backend that cannot keep media private.

Documents 0.13.1 Updated Edit this page Report a problem

On this page

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-blob

Configure

# .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.