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

Azure Blob Storage

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

Containers rather than buckets, and a capability that depends on how you authenticate.

Documents 0.13.1 Updated Edit this page Report a problem

On this page

Azure Blob has no S3 compatibility, so it has its own adapter.

  • id: azure
  • Capabilities: { directUrl: <computed>, contentTypeMetadata: true, streamingPut: true }

Install

npm install @orthacms/media-provider-azure

Configure

# .env
MEDIA_AZURE_CONTAINER=media
# from the portal: Storage account → Access keys → Connection string
MEDIA_AZURE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=acmecms;AccountKey=...;EndpointSuffix=core.windows.net
# optional: redirect downloads to a SAS URL (needs an AccountKey)
MEDIA_DIRECT_SERVE=signed-url

Both variables are required in a generated app’s config/media-storage.ts (the reference server ships with local disk):

import type { AzureStorageConfig } from '@orthacms/media-provider-azure';
import { requireEnv } from '@orthacms/utils-server';

export function mediaStorage(): AzureStorageConfig {
    return {
        container: requireEnv('MEDIA_AZURE_CONTAINER'),
        connectionString: requireEnv('MEDIA_AZURE_CONNECTION_STRING')
    };
}

All options

AzureStorageConfig: container (required), connectionString?, accountName?, accountKey?, keyPrefix?, containerClient?. One of the three credential shapes is required, or the factory throws.

Three ways to connect

ShapedirectUrl
connectionString containing AccountKeytrue
connectionString with a SAS tokenfalse
accountName + accountKeytrue
containerClient (e.g. managed identity)false
// account name + key
createAzureStorageProvider({
    container: 'media',
    accountName: 'acmecms',
    accountKey: process.env.AZURE_STORAGE_KEY!
});
// managed identity — build the client yourself
import { DefaultAzureCredential } from '@azure/identity';
import { BlobServiceClient } from '@azure/storage-blob';

const containerClient = new BlobServiceClient(
    'https://acmecms.blob.core.windows.net',
    new DefaultAzureCredential()
).getContainerClient('media');

createAzureStorageProvider({ container: 'media', containerClient });

Managed identity cannot sign a SAS

With a containerClient, directUrl is false, so MEDIA_DIRECT_SERVE=signed-url is a boot error. Downloads are proxied.

Register

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

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

Verify

verify() reads the container’s properties at boot. A wrong container, dead account or expired key refuses the start.

Locally, with Azurite:

docker run -d -p 10000:10000 mcr.microsoft.com/azure-storage/azurite azurite-blob --blobHost 0.0.0.0
az storage container create --name media \
  --connection-string "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"

Use the same connection string as MEDIA_AZURE_CONNECTION_STRING. Upload a file (curl example), then:

az storage blob list --container-name media --connection-string "$MEDIA_AZURE_CONNECTION_STRING" -o table

With signed-url, the download’s Location is a SAS URL that pins rscd (disposition) and rsct (content type).

Behaviour worth knowing

  • A failed upload deletes the blob, so no uncommitted blocks are left.
  • BlobNotFound / 404 → ObjectNotFoundError (HTTP 404). Throttling and auth errors are rethrown, not disguised as missing.
  • A user-delegation SAS (signing with managed identity) is not implemented.