---
url: /en/docs/media/azure-blob-storage.md
description: >-
  Use Azure Blob Storage as an external media storage provider in Sveltia CMS
  with a shared access signature, no backend proxy required.
---

# Azure Blob Storage Integration

[Azure Blob Storage](https://azure.microsoft.com/products/storage/blobs/) is Microsoft’s object storage service for unstructured data. Unlike most of the other object storage services Sveltia CMS supports, Blob Storage does not offer an S3-compatible API, so the CMS talks to the [Blob Service REST API](https://learn.microsoft.com/en-us/rest/api/storageservices/blob-service-rest-api) directly. Requests are authorized with a [shared access signature (SAS)](https://learn.microsoft.com/en-us/azure/storage/common/storage-sas-overview) token, and files are uploaded straight from the browser to Azure — no backend proxy is required.

## Requirements

* An Azure storage account with a blob container created.
* A SAS token with the Read, Write, Create, List and Delete permissions on that container (see [Credentials](#credentials) below).
* A CORS rule on the storage account’s Blob service (see [CORS](#cors) below).
* A `public_url` configured, unless the container allows anonymous read access (see [Public Read Access](#public-read-access) below).

### CSP

If your site uses a Content Security Policy (CSP), you need to allow the Blob service endpoint and your public URL. See [Content Security Policy](#content-security-policy) below for details.

## Setup

### Credentials

Azure storage account keys can’t be scoped to a single container — they grant full control over the entire storage account — so Sveltia CMS uses a SAS token instead. A SAS is limited to the permissions and expiry you choose, and it’s the only credential the CMS needs.

Create a **service SAS** for the container via **Azure Portal > Storage account > Data storage > Containers > \[container] > Shared access tokens**:

* **Signing method**: Account key (or User delegation key; see the note below)
* **Permissions**: Read, Create, Write, List, Delete. The Delete permission is only needed to delete and rename blobs from the [Asset Library](/en/docs/ui/asset-library); leave it out if you don’t want editors to remove files
* **Expiry**: as far out as your security policy allows (see [Token Expiry](#token-expiry) below)
* **Allowed protocols**: HTTPS only

Click **Generate SAS token and URL** and copy the **Blob SAS token** — the query string that starts with `sv=`, not the full URL below it.

An **account SAS**, created under **Storage account > Security + networking > Shared access signature**, works as well. It needs **Allowed services**: Blob, **Allowed resource types**: Container and Object, and the same permissions as above.

The token is entered by users in the CMS UI when they access the media library for the first time — it is never stored in config. See [Accessing the Storage](#accessing-the-storage) below.

::: info Accounts that disallow Shared Key

If your storage account has **Allow storage account key access** disabled, neither a service SAS nor an account SAS signed with the account key will be accepted. Generate a [user delegation SAS](https://learn.microsoft.com/en-us/rest/api/storageservices/create-user-delegation-sas), which is signed with Microsoft Entra ID credentials instead:

```sh
az storage container generate-sas \
  --account-name mystorageaccount \
  --name my-container \
  --permissions rcwld \
  --expiry 2026-12-31T00:00Z \
  --auth-mode login --as-user \
  --https-only --output tsv
```

A user delegation SAS is valid for a maximum of seven days.

:::

### Token Expiry

Every SAS token has an expiry date. Once it passes, the media library stops loading with an authorization error, and users need to generate a new token and enter it again in the CMS. Choose an expiry that balances your security policy against how often your editors want to repeat that step.

### Public Read Access

Blob containers are private by default, and new storage accounts have anonymous access disabled at the account level. This matters because the URL Sveltia CMS writes into your entries can’t contain the SAS token — the token expires, and the URL would break with it.

Two options are available:

**Option A — Anonymous read access on the container:**

1. In **Storage account > Settings > Configuration**, set **Allow blob anonymous access** to **Enabled**.
2. In **Containers > \[container] > Change access level**, choose **Blob (anonymous read access for blobs only)**.

Asset URLs are then the blob endpoint URLs, and `public_url` is optional.

**Option B — CDN or custom domain (recommended for production):**

Put [Azure CDN](https://learn.microsoft.com/en-us/azure/cdn/) or [Azure Front Door](https://learn.microsoft.com/en-us/azure/frontdoor/) in front of the container, then set that hostname as `public_url` in your config:

```yaml
public_url: 'https://media.example.com'
```

Asset URLs are constructed as `{public_url}/{blob name}`, using the full blob name, including any `prefix`.

::: tip Previews vs. stored URLs

When `public_url` is omitted, previews within the CMS still work against a private container, because the SAS token is appended to the preview URL. The URL saved into your entries is the plain blob URL without the token, so it only resolves for your site’s visitors if the container allows anonymous read access.

:::

### CORS

Configure cross-origin resource sharing under **Storage account > Settings > Resource sharing (CORS) > Blob service**. CORS is required because Sveltia CMS reads and writes blobs directly from the browser, and the `x-ms-blob-type` header it sends on uploads triggers a preflight request.

| Setting | Value |
| --- | --- |
| Allowed origins | `https://your-cms-domain.com` |
| Allowed methods | `GET`, `HEAD`, `OPTIONS`, `PUT`, `DELETE` |
| Allowed headers | `*` (or `x-ms-blob-type,x-ms-copy-source,content-type`) |
| Exposed headers | `*` |
| Max age | `3600` |

The equivalent Azure CLI command:

```sh
az storage cors add \
  --services b \
  --methods GET HEAD OPTIONS PUT DELETE \
  --origins https://your-cms-domain.com \
  --allowed-headers '*' \
  --exposed-headers '*' \
  --max-age 3600 \
  --account-name mystorageaccount
```

CORS rules are set per storage account, not per container, and they apply to the Blob service as a whole.

The `DELETE` method and the `x-ms-copy-source` header are needed to delete and rename blobs from the [Asset Library](/en/docs/ui/asset-library). If you configured CORS before these operations became available, add them to your existing rule, otherwise the browser will block those requests during the preflight check.

## Configuration

Here’s an example configuration for Azure Blob Storage:

::: code-group

```yaml [YAML]
media_libraries:
  azure_blob_storage:
    account_name: mystorageaccount
    container: my-container
    public_url: https://media.example.com # Optional
    prefix: cms-uploads/ # Optional
```

```toml [TOML]
[media_libraries.azure_blob_storage]
account_name = "mystorageaccount"
container = "my-container"
public_url = "https://media.example.com" # Optional
prefix = "cms-uploads/" # Optional
```

```json [JSON]
{
  "media_libraries": {
    "azure_blob_storage": {
      "account_name": "mystorageaccount",
      "container": "my-container",
      "public_url": "https://media.example.com",
      "prefix": "cms-uploads/"
    }
  }
}
```

```js [JavaScript]
{
  media_libraries: {
    azure_blob_storage: {
      account_name: 'mystorageaccount',
      container: 'my-container',
      public_url: 'https://media.example.com', // Optional
      prefix: 'cms-uploads/', // Optional
    },
  },
}
```

:::

::: warning

Do not write your SAS token in the configuration file, as it should be kept confidential and not exposed in client-side code. Users will be prompted to enter the token when they use the storage for the first time, which will be stored securely in the browser’s local storage.

:::

### Configuration Properties

| Property | Required | Description |
| --- | --- | --- |
| `account_name` | Yes | The storage account name. Used to construct the Blob service endpoint, `https://{account_name}.blob.core.windows.net`. Not required if `endpoint` is given. |
| `container` | Yes | The blob container name. |
| `public_url` | No | Public URL for asset downloads. Required unless the container allows anonymous read access, because the URL stored in your entries can’t contain the SAS token. |
| `prefix` | No | Path prefix within the container, e.g. `uploads/`. A trailing slash is added if missing. |
| `endpoint` | No | Custom Blob service endpoint, including the account path where applicable, e.g. the [Azurite emulator](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite) URL `http://127.0.0.1:10000/devstoreaccount1`. Overrides `account_name`. |

## Content Security Policy

Allow the Blob service endpoint for your storage account:

```
connect-src https://mystorageaccount.blob.core.windows.net;
img-src     https://mystorageaccount.blob.core.windows.net;
```

If you serve assets through a CDN or custom domain via `public_url`, add that host to `connect-src` and use it for `img-src` instead:

```
connect-src https://mystorageaccount.blob.core.windows.net
            https://media.example.com;
img-src     https://media.example.com;
```

The asset host must be in `connect-src` because the Download and Copy buttons and the text preview in the [Asset Library](/en/docs/ui/asset-library) fetch the file from its public URL. For the same reason, a CDN or custom domain must also send CORS headers that allow the CMS origin, otherwise downloads fail.

Replace `mystorageaccount` with your actual storage account name.

See the [CSP documentation](/en/docs/security#setting-up-content-security-policy) for more details.

## Accessing the Storage

There are two ways to use Azure Blob Storage in Sveltia CMS:

### File and Image Fields

When editing content entries, users can use [File](/en/docs/fields/file) and [Image](/en/docs/fields/image) fields to upload and select media on Azure Blob Storage directly within the entry editor. After entering the SAS token in the CMS UI when prompted — or at any time under **Settings > Media > Cloud Storage Service API Keys** — users can upload new media directly to Azure or select existing media from the container.

When uploading media, files are stored in the container as block blobs, and the CMS can take advantage of Azure’s capabilities directly from the CMS. You can also select existing media from your Blob Storage.

### Asset Library

Azure Blob Storage also appears under **External Locations** in the [Asset Library](/en/docs/ui/asset-library), where users can browse, search, sort and filter the blobs in your container, and upload, rename, replace, download or delete them without leaving the CMS. Deleting requires a SAS token with the Delete permission, and so does renaming, which copies the blob to the new name and then deletes the original. The blobs are browsed [folder by folder](/en/docs/ui/asset-library#folders-on-external-locations) by their virtual directories: subfolders are listed ahead of the blobs, uploads go to the folder being viewed, and folders can be created, renamed and deleted — an empty folder is kept with a zero-byte placeholder blob named after it with a trailing slash, and renaming a folder copies and deletes each blob in it.
