---
title: "Azure Blob Storage files"
description: "Store files in an Azure Blob Storage container with @mayurajs/filestorage-azure-blob: Shared Key, Entra ID or SAS, preconditions and errors."
package: "@mayurajs/filestorage-azure-blob"
vendor: "Microsoft Azure"
logo: "azure"
website: "https://azure.microsoft.com/products/storage/blobs"
area: "files"
---

[Azure Blob Storage](https://azure.microsoft.com/products/storage/blobs) is Microsoft Azure's object store.
`@mayurajs/filestorage-azure-blob` plugs a container into [`mayura/files`](../guides/files.md), so you get the same
`createFileStore` API, limits, per-tenant views and file tools as with the built-in S3 backend. It talks to the Blob
REST API (service version `2024-11-04`) with `fetch`, signing with Web Crypto: no Azure SDK, no other dependencies, and
it runs on every runtime Mayura runs on.

## Install

```bash
npm install mayura @mayurajs/filestorage-azure-blob
```

## Credentials

Give exactly one of three credentials, all from the storage account in the [Azure portal](https://portal.azure.com):

- **Account key** (`accountKey`): under **Security + networking > Access keys**. Requests are signed with Shared Key.
  The key grants everything in the account.
- **Microsoft Entra ID token** (`token`): a function returning an access token for `https://storage.azure.com/`, for
  example from `@azure/identity`. Give the identity the **Storage Blob Data Contributor** role on the container.
- **Shared access signature** (`sas`): under **Shared access tokens**, or generated by your code. The store reads,
  creates, writes, deletes and lists, so the SAS needs those permissions (`sp=rcwdl`) on the container.

The package reads nothing from the environment, neither `AZURE_STORAGE_CONNECTION_STRING` nor `AZURE_STORAGE_KEY`:
pass the credential yourself, for example from `AZURE_STORAGE_ACCOUNT`, `AZURE_STORAGE_KEY` (or
`AZURE_STORAGE_SAS_TOKEN`) and `AZURE_STORAGE_CONTAINER`.

## Use it

```ts
import { createFileStore } from 'mayura/files';
import { azureBlobFiles } from '@mayurajs/filestorage-azure-blob';

const files = createFileStore(azureBlobFiles({
  account: process.env.AZURE_STORAGE_ACCOUNT ?? 'acmestorage',
  container: process.env.AZURE_STORAGE_CONTAINER ?? 'reports',
  accountKey: process.env.AZURE_STORAGE_KEY ?? '',
}), { maxFileBytes: 10 * 1024 * 1024 });

const written = await files.put('q3/sales.csv', new TextEncoder().encode('order,total\n'), {
  contentType: 'text/csv',
  metadata: { 'run-id': 'run-42' },
  ifNoneMatch: '*',
});
const file = await files.get('q3/sales.csv', { ifMatch: written.etag });
console.log(file?.metadata); // { 'run-id': 'run-42' }
```

With a SAS, pass `sas: 'sv=...&sp=rcwdl&sig=...'` (the query string, without a leading `?`) instead of `accountKey`.
See [Files](../guides/files.md) for every store method, the store's own options and the file tools.

## Options

| Option | Default | Meaning |
| --- | --- | --- |
| `account` | required | The storage account name: 3 to 24 lowercase letters and digits. |
| `container` | required | The container name. |
| `accountKey` | one of three | The base64 account key, for Shared Key signing. |
| `token` | one of three | A function returning a Microsoft Entra ID access token for `https://storage.azure.com/`, called for each request. |
| `sas` | one of three | A shared access signature: the query string Azure gave, without a leading `?`, containing `sig`. |
| `endpoint` | `https://<account>.blob.core.windows.net` | Another endpoint: an https origin, or an http one on this machine for Azurite, optionally with the account as its path (`http://127.0.0.1:10000/devstoreaccount1`). |
| `maxListResponseBytes` | 16 MiB | The largest list response read. |
| `fetch` | `globalThis.fetch` | For tests and proxies. |

## Versions and preconditions

A file's `etag` is Azure's ETag, quoted. The store's `conditionalWrites` and `conditionalDelete` are both `true`, and
they are Azure's own conditional headers:

- `ifNoneMatch: '*'` sends `If-None-Match: *`; Azure's answer that the blob already exists becomes `CONFLICT`.
- `ifMatch` on a write, a read or a delete sends `If-Match`; a stale etag (HTTP 412) or a missing blob becomes
  `CONFLICT`.

## Writes, metadata and limits

- Files are block blobs, each written in one request. A delete also removes the blob's snapshots.
- The media type is the blob's `Content-Type`. Metadata names must be C# identifiers on Azure, so each key is stored
  under a name Azure accepts (`run-id` as `m_run_id`) and read back as written. Listings return each file's size, etag,
  media type and time, but not its metadata.
- Files are as large as the store's `maxFileBytes`, up to 5 GiB. Each file is held in memory while it is written or read.
- Ranges are read with `x-ms-range`; a range that starts at or past the end returns no bytes and the file's size.

## Errors

Failures reach you as a `FileStoreError` (code `STORAGE_UNAVAILABLE`) with a `reason`, mapped from Azure's answers.
Nothing Azure wrote reaches the message.

| Error | When |
| --- | --- |
| `CONFLICT` | A precondition failed: the blob exists for a create-only write, has another version, or is missing for `ifMatch`. |
| `reason: 'authentication'` | The token source threw or returned no usable token (nothing is sent), or Azure answered 401 or 403, for example a wrong key or signature. |
| `reason: 'rate_limited'` | HTTP 429. |
| `reason: 'timeout'` | HTTP 408, or the call took longer than the store's `timeoutMs`. |
| `reason: 'unavailable'` | HTTP 5xx, or Azure could not be reached. |
| `reason: 'rejected'` | Azure refused the request for another reason. |
| `reason: 'invalid_response'` | The reply was not what the Blob API promises. |
| `LIMIT_EXCEEDED` | A file or range is larger than the limit for the call. |

Configuration mistakes, such as two credentials at once, an account key that is not base64 or an http endpoint on
another host, fail with `INVALID_CONFIG` when you call `azureBlobFiles()`.

## How it is tested

- Unit tests run the package against a fake that records every request: Shared Key signatures, block blob headers,
  metadata names, conditional headers, Entra ID and SAS authentication, error mapping without the service's text, and
  that nothing is read from the environment.
- In CI, the [file store conformance suite](../guides/files.md#writing-a-backend) runs against Azurite, Microsoft's
  Azure Storage emulator, which checks Shared Key signatures: all 13 cases pass, and a request signed with another key is
  refused with `authentication`.
- It has not been run against a real Azure account yet.

## Related

- [Files](../guides/files.md): stores, views, preconditions and file tools.
- [Artifacts](../guides/artifacts.md#in-s3-r2-and-other-file-stores): keep agent artifacts in this container.
- [Google Cloud Storage files](filestorage-gcs.md): the same store on Google Cloud.
- [Files SDK files](filestorage-files-sdk.md): other providers through Files SDK.
