npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

strapi-provider-upload-azure-blob

v1.0.1

Published

Strapi v5 upload provider for Azure Blob Storage: every auth method, private containers with signed URLs, CDN support.

Readme

strapi-provider-upload-azure-blob

Azure Blob Storage upload provider for Strapi v5.

  • Every Azure auth method: account key, connection string, SAS token, managed identity, workload identity, service principal (secret or certificate), DefaultAzureCredential, or your own TokenCredential.
  • Private containers: Strapi serves short-lived, read-only signed URLs. You don't need anonymous access or a CDN.
  • CDN / Azure Front Door URLs, with or without the container in the path.
  • Follows the Strapi provider contract: upload() accepts a buffer and uploadStream() accepts a stream, so plugins that re-upload files (image converters, migrations) work.
  • Safe deletes: deleting a file whose blob is already gone is not an error, and each blob is found from stored metadata, so changing basePath later never orphans old files.
  • Validates config at startup with clear error messages, ships TypeScript types, and is tested against Azurite and a real storage account.

Requires Node.js 22+ and Strapi 5.

Install

npm install strapi-provider-upload-azure-blob
# or
yarn add strapi-provider-upload-azure-blob

Quick start

config/plugins.ts:

export default ({ env }) => ({
  upload: {
    config: {
      provider: 'strapi-provider-upload-azure-blob',
      providerOptions: {
        account: env('AZURE_STORAGE_ACCOUNT'),
        containerName: env('AZURE_STORAGE_CONTAINER'),
        auth: { type: 'accountKey', accountKey: env('AZURE_STORAGE_ACCOUNT_KEY') },
        basePath: 'uploads',
        cdnBaseURL: env('AZURE_CDN_BASE_URL'),
        privateContainer: env.bool('AZURE_PRIVATE_CONTAINER', false),
        cacheControl: 'public, max-age=31536000, immutable',
      },
    },
  },
});

Then allow the media host in the admin's Content Security Policy (see Admin previews).

Options

| Option | Type | Default | Description | |---|---|---|---| | containerName | string | required | Container that holds the uploads. | | auth | object | required | How to sign in to Azure. See Authentication. | | account | string | — | Storage account name. Required for accountKey; otherwise needed unless serviceBaseURL or a connection string identifies the account. | | serviceBaseURL | string | https://<account>.blob.core.windows.net | Override for Azurite, sovereign clouds or private endpoints. | | basePath | string | '' | Folder prefix for every blob, e.g. uploads. | | cdnBaseURL | string | — | Public host in front of the account (Azure Front Door, CDN, custom domain). Stored file URLs use this host. | | cdnPathIncludesContainer | boolean | true | true: https://cdn/<container>/<blob>. false: https://cdn/<blob>, when the CDN route already points at the container. | | privateContainer | boolean | false | Serve files through signed URLs. See Private containers. | | signedUrlExpiresIn | number | 900 | Signed URL lifetime in seconds. | | cacheControl | string | — | Cache-Control header stored on every blob. | | createContainerIfNotExists | boolean | false | Create the container on first upload. | | containerAccessLevel | 'private' \| 'blob' \| 'container' | 'private' | Access level used only when this provider creates the container. | | uploadOptions.bufferSize | number | 4194304 | Block size in bytes for parallel uploads. | | uploadOptions.maxConcurrency | number | 5 | Blocks uploaded in parallel. |

Boolean and number options also accept strings such as 'true' and '900', so raw env() values work.

Per-call options set in Strapi's actionOptions.upload / actionOptions.uploadStream are passed to Azure: blobHTTPHeaders, metadata, tags, tier.

Authentication

| auth.type | Fields | Use when | |---|---|---| | accountKey | accountKey (+ top-level account) | Simplest setup with a storage account key. | | connectionString | connectionString | You already have a connection string. The account is read from it. | | sasToken | sasToken | Limited-permission access. The token needs r, w, c and d on blob objects. | | managedIdentity | clientId? | Strapi runs on Azure (App Service, Container Apps, VM). Omit clientId for a system-assigned identity. | | workloadIdentity | clientId?, tenantId?, tokenFilePath? | AKS with workload identity. Defaults come from the standard AZURE_* env vars. | | servicePrincipal | tenantId, clientId, clientSecret | App registration with a client secret. | | clientCertificate | tenantId, clientId, certificatePath, certificatePassword? | App registration with a certificate. | | defaultAzureCredential | managedIdentityClientId?, tenantId? | Let the Azure SDK pick: env vars, workload identity, managed identity, Azure CLI, and so on. Good for local development with az login. | | tokenCredential | credential | Any TokenCredential instance you construct yourself. |

Identities (every type except accountKey, connectionString and sasToken) need the Storage Blob Data Contributor role on the container or account. That role also allows creating user delegation keys for private mode.

// Managed identity, no secrets in env
auth: { type: 'managedIdentity', clientId: env('AZURE_CLIENT_ID') }

// Service principal
auth: {
  type: 'servicePrincipal',
  tenantId: env('AZURE_TENANT_ID'),
  clientId: env('AZURE_CLIENT_ID'),
  clientSecret: env('AZURE_CLIENT_SECRET'),
}

Private containers

With privateContainer: true, Strapi keeps the plain URL in the database and signs it each time it returns a file, both in the admin and in the Content API. Each signed URL:

  • is read-only (sp=r) and covers one blob,
  • expires after signedUrlExpiresIn seconds,
  • uses the CDN host when cdnBaseURL is set, so the CDN must forward query strings.

How URLs are signed depends on the auth type:

| Auth | Signing | |---|---| | accountKey, connectionString with a key | Service SAS signed with the account key. | | Any identity-based type | User delegation SAS. One delegation key is cached for 24 hours. | | sasToken | The configured token is appended as-is, so its own permissions and expiry apply. |

If your frontend caches pages (SSG/ISR) longer than signedUrlExpiresIn, image URLs on those pages will expire. Either raise signedUrlExpiresIn or keep the container public behind a CDN.

CDN / Azure Front Door

Set cdnBaseURL to the public host. Stored URLs then look like:

cdnPathIncludesContainer: true  -> https://media.example.com/<container>/uploads/photo_abc123.jpg
cdnPathIncludesContainer: false -> https://media.example.com/uploads/photo_abc123.jpg

Trailing slashes in cdnBaseURL and serviceBaseURL are removed automatically.

Admin previews (CSP)

The Media Library loads images from your storage or CDN host, so add that host to Strapi's security middleware in config/middlewares.ts:

const mediaHost = process.env.AZURE_CDN_BASE_URL || `https://${process.env.AZURE_STORAGE_ACCOUNT}.blob.core.windows.net`;

export default [
  'strapi::logger',
  'strapi::errors',
  {
    name: 'strapi::security',
    config: {
      contentSecurityPolicy: {
        useDefaults: true,
        directives: {
          'img-src': ["'self'", 'data:', 'blob:', 'market-assets.strapi.io', mediaHost],
          'media-src': ["'self'", 'data:', 'blob:', mediaHost],
        },
      },
    },
  },
  // ...the rest of the default middlewares
];

How files are stored

Blob name: <basePath>/<file.path>/<hash><ext>, with empty parts and duplicate slashes removed. The provider saves { container, blobName } in each file's provider_metadata, including thumbnails and other formats, and uses it to delete and sign. Files without metadata, such as those uploaded by another provider, are found from their URL instead.

Migrating from strapi-provider-upload-azure-storage

| Old option | New option | |---|---| | authType: 'default' + accountKey | auth: { type: 'accountKey', accountKey } | | authType: 'default' + sasToken | auth: { type: 'sasToken', sasToken } | | authType: 'msi' + clientId | auth: { type: 'defaultAzureCredential', managedIdentityClientId: clientId } (same behaviour), or managedIdentity to use only the managed identity | | defaultPath | basePath | | cdnBaseURL | cdnBaseURL | | removeCN: 'true' | cdnPathIncludesContainer: false | | defaultCacheControl | cacheControl | | createContainerIfNotExist | createContainerIfNotExists | | publicAccessType | containerAccessLevel | | uploadOptions.maxBuffers | uploadOptions.maxConcurrency |

Set basePath to your old defaultPath so new uploads land in the same folder.

Then update the provider name on existing files. Strapi stores the provider name on every file and only deletes or signs files whose name matches the provider that is currently configured. Files uploaded by the old package are otherwise skipped silently: deleting them in the Media Library leaves the blob behind, and private mode doesn't sign their URLs. Back up the database, then run:

UPDATE files
SET provider = 'strapi-provider-upload-azure-blob'
WHERE provider = 'strapi-provider-upload-azure-storage';

After that, old files work like new ones: their URLs already point at the same blobs, and the provider recovers each blob name from the URL.

Troubleshooting

| Symptom | Cause | Fix | |---|---|---| | Media URLs return 409 PublicAccessNotPermitted | The storage account blocks anonymous access. | Use privateContainer: true, put a CDN in front, or allow anonymous blob access on the account and set the container to blob. | | Upload fails with ContainerNotFound | The container doesn't exist. | Create it, or set createContainerIfNotExists: true. | | 403 AuthorizationPermissionMismatch | The identity lacks data-plane permission. | Assign Storage Blob Data Contributor. Management roles such as Owner are not enough. | | Images missing in the admin only | CSP blocks the media host. | See Admin previews. | | Startup error starting with [strapi-provider-upload-azure-blob] | Invalid or missing option. | The message names the field to fix. |

Development

yarn install
yarn typecheck
yarn test     # starts Azurite automatically on port 10010
yarn build

License

MIT