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

Local filesystem

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

The default install — a directory on disk, and the one thing to change before production.

Documents 0.13.1 Updated Edit this page Report a problem

On this page

The default backend: blobs streamed to a directory. Depends only on Node built-ins and @orthacms/media-domain.

  • id: local
  • Capabilities: { directUrl: false, contentTypeMetadata: false, streamingPut: true }

Install

npm install @orthacms/media-provider-local

Configure

# .env — point at a persistent volume in production
MEDIA_LOCAL_ROOT=/var/lib/orthacms/media
VariableDefaultNotes
MEDIA_LOCAL_ROOT./.storage/mediaResolved once, at construction

The reference server, apps/server/config/media.ts (a generated app has the same in config/media-storage.ts):

storage: {
    rootDir: readEnv('MEDIA_LOCAL_ROOT') ?? './.storage/media'
},

LocalStorageConfig is { rootDir } and nothing else.

The default root is ephemeral

./.storage/media is inside the container. A new container starts empty while the database still lists every asset, so every download 404s — silently. Mount a volume before uploading anything you care about.

# docker-compose.yml
services:
  cms:
    environment:
      MEDIA_LOCAL_ROOT: /data/media
    volumes:
      - media:/data/media
volumes:
  media:

Register

// apps/server/src/plugins.ts
import { createLocalStorageProvider } from '@orthacms/media-provider-local';

MediaServerPlugin({
    provider: createLocalStorageProvider(config.plugins.media.storage),
    config: config.plugins.media
});

Verify

Upload a file (curl example), then:

ls -R "$MEDIA_LOCAL_ROOT"
# <workspaceId>/<assetId>/kingfisher.jpg
# <workspaceId>/<assetId>/variants/thumb.webp …

MEDIA_DIRECT_SERVE=signed-url is a boot error here

A filesystem cannot sign a URL. Downloads always stream through the app.

Behaviour worth knowing

  • Keys are <workspaceId>/<assetId>/<sanitized-filename>; derivatives go under variants/, so a derivative never overwrites an original.
  • put is all-or-nothing: written to a temp file, then one rename. A failure removes the temp file and any directories it created.
  • Every key is checked against the root. A key resolving outside it throws StorageKeyOutsideRootError.
  • Content type is kept in the database, not on disk.
  • Backups: the database alone restores rows pointing at missing bytes. Back up the media root with it.