Back to Payload

Storage Adapters

docs/upload/storage-adapters.mdx

3.84.126.5 KB
Original Source

Payload offers additional storage adapters to handle file uploads. These adapters allow you to store files in different locations, such as Amazon S3, Vercel Blob Storage, Google Cloud Storage, and more.

ServicePackage
Vercel Blob@payloadcms/storage-vercel-blob
AWS S3@payloadcms/storage-s3
Azure@payloadcms/storage-azure
Google Cloud Storage@payloadcms/storage-gcs
Uploadthing@payloadcms/storage-uploadthing
R2@payloadcms/storage-r2

Vercel Blob Storage

@payloadcms/storage-vercel-blob

Installation#vercel-blob-installation

sh
pnpm add @payloadcms/storage-vercel-blob

Usage#vercel-blob-usage

  • Configure the collections object to specify which collections should use the Vercel Blob adapter. The slug must match one of your existing collection slugs.
  • Ensure you have BLOB_READ_WRITE_TOKEN set in your Vercel environment variables. This is usually set by Vercel automatically after adding blob storage to your project.
  • When enabled, this package will automatically set disableLocalStorage to true for each collection.
  • When deploying to Vercel, server uploads are limited with 4.5MB. Set clientUploads to true to do uploads directly on the client.
ts
import { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'
import { Media } from './collections/Media'
import { MediaWithPrefix } from './collections/MediaWithPrefix'

export default buildConfig({
  collections: [Media, MediaWithPrefix],
  plugins: [
    vercelBlobStorage({
      enabled: true, // Optional, defaults to true
      // Specify which collections should use Vercel Blob
      collections: {
        media: true,
        'media-with-prefix': {
          prefix: 'my-prefix',
        },
      },
      // Token provided by Vercel once Blob storage is added to your Vercel project
      token: process.env.BLOB_READ_WRITE_TOKEN,
    }),
  ],
})

Configuration Options#vercel-blob-configuration

OptionDescriptionDefault
enabledWhether or not to enable the plugintrue
collectionsCollections to apply the Vercel Blob adapter to
addRandomSuffixAdd a random suffix to the uploaded file name in Vercel Blob storagefalse
cacheControlMaxAgeCache-Control max-age in seconds365 * 24 * 60 * 60 (1 Year)
tokenVercel Blob storage read/write token''
clientUploadsDo uploads directly on the client to bypass limits on Vercel.
useCompositePrefixesCombine collection prefix with document prefix instead of document prefix overriding it.false

S3 Storage

@payloadcms/storage-s3

Installation#s3-installation

sh
pnpm add @payloadcms/storage-s3

Usage#s3-usage

  • Configure the collections object to specify which collections should use the S3 Storage adapter. The slug must match one of your existing collection slugs.
  • The config object can be any S3ClientConfig object (from @aws-sdk/client-s3). This is highly dependent on your AWS setup. Check the AWS documentation for more information.
  • When enabled, this package will automatically set disableLocalStorage to true for each collection.
  • When deploying to Vercel, server uploads are limited with 4.5MB. Set clientUploads to true to do uploads directly on the client. You must allow CORS PUT method for the bucket to your website.
  • Configure signedDownloads (either globally or per-collection in collections) to use presigned URLs for files downloading. This can improve performance for large files (like videos) while still respecting your access control. Additionally, with signedDownloads.shouldUseSignedURL you can specify a condition whether Payload should use a presigned URL, if you want to use this feature only for specific files.
  • You can conditionally enable the plugin using the enabled option. For example, enabled: Boolean(process.env.S3_BUCKET) skips the plugin in local development when credentials are not set.
ts
import { s3Storage } from '@payloadcms/storage-s3'
import { Media } from './collections/Media'
import { MediaWithPrefix } from './collections/MediaWithPrefix'

export default buildConfig({
  collections: [Media, MediaWithPrefix],
  plugins: [
    s3Storage({
      collections: {
        media: true,
        'media-with-prefix': {
          prefix,
        },
        'media-with-presigned-downloads': {
          // Filter only mp4 files
          signedDownloads: {
            shouldUseSignedURL: ({ collection, filename, req }) => {
              return filename.endsWith('.mp4')
            },
          },
        },
      },
      bucket: process.env.S3_BUCKET,
      config: {
        credentials: {
          accessKeyId: process.env.S3_ACCESS_KEY_ID,
          secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
        },
        region: process.env.S3_REGION,
        // ... Other S3 configuration
      },
    }),
  ],
})

Configuration Options#s3-configuration

OptionDescriptionDefault
enabledWhether or not to enable the plugintrue
collectionsCollections to apply the S3 adapter to
bucketThe name of the S3 bucket
configS3ClientConfig object passed to the AWS SDK client
aclAccess control list for uploaded files (e.g. 'public-read')undefined
clientUploadsDo uploads directly on the client to bypass Vercel's 4.5MB server limit
signedDownloadsUse presigned URLs for file downloads. Can be overridden per collection
useCompositePrefixesCombine collection prefix with document prefix instead of document prefix overriding it.false

For full S3ClientConfig options, see the AWS SDK Package and S3ClientConfig docs.

Using with Cloudflare R2 (via S3 API)#s3-r2

Cloudflare R2 exposes an S3-compatible API, so you can use @payloadcms/storage-s3 to connect to it. This is the recommended approach when deploying to Vercel, Netlify, or any Node.js environment. (The @payloadcms/storage-r2 adapter is for Cloudflare Workers only, where R2 is available as a native bucket binding.)

<Banner type="warning"> R2 buckets are **private by default**. The S3 API endpoint (e.g. `https://<accountId>.r2.cloudflarestorage.com`) is used for **uploading files** only — it cannot serve files publicly. To serve files, enable the **R2.dev subdomain** or connect a **custom domain** to your bucket in the Cloudflare dashboard, then pass that public URL to `generateFileURL`. </Banner>
ts
import { s3Storage } from '@payloadcms/storage-s3'

export default buildConfig({
  collections: [Media],
  plugins: [
    s3Storage({
      enabled: Boolean(process.env.R2_BUCKET),
      collections: {
        media: {
          disablePayloadAccessControl: true,
          generateFileURL: ({ filename, prefix }) => {
            const key = prefix ? `${prefix}/${filename}` : filename
            return `${process.env.R2_PUBLIC_URL}/${key}`
          },
        },
      },
      bucket: process.env.R2_BUCKET,
      config: {
        credentials: {
          accessKeyId: process.env.R2_ACCESS_KEY_ID,
          secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
        },
        region: 'auto',
        // R2 S3 API endpoint — for uploads only, not for serving files
        endpoint: process.env.R2_ENDPOINT,
        forcePathStyle: true,
      },
    }),
  ],
})

Required environment variables:

R2_BUCKET=my-bucket
R2_ACCESS_KEY_ID=...
R2_SECRET_ACCESS_KEY=...
R2_ENDPOINT=https://<accountId>.r2.cloudflarestorage.com
R2_PUBLIC_URL=https://media.yourdomain.com
  • region: 'auto' — required by R2; standard AWS region values are not accepted
  • endpoint — your R2 S3 API endpoint from the Cloudflare dashboard. This is for uploads only — not for serving files
  • forcePathStyle: true — required for R2's path-style bucket addressing
  • R2_PUBLIC_URL — your bucket's public URL (either the *.r2.dev subdomain or a custom domain you've connected in Cloudflare). This is separate from the S3 API endpoint
  • disablePayloadAccessControl: true — bypasses Payload's file proxy so URLs point directly to your public R2 domain

Azure Blob Storage

@payloadcms/storage-azure

Installation#azure-installation

sh
pnpm add @payloadcms/storage-azure

Usage#azure-usage

  • Configure the collections object to specify which collections should use the Azure Blob adapter. The slug must match one of your existing collection slugs.
  • When enabled, this package will automatically set disableLocalStorage to true for each collection.
  • When deploying to Vercel, server uploads are limited with 4.5MB. Set clientUploads to true to do uploads directly on the client. You must allow CORS PUT method to your website.
ts
import { azureStorage } from '@payloadcms/storage-azure'
import { Media } from './collections/Media'
import { MediaWithPrefix } from './collections/MediaWithPrefix'

export default buildConfig({
  collections: [Media, MediaWithPrefix],
  plugins: [
    azureStorage({
      collections: {
        media: true,
        'media-with-prefix': {
          prefix,
        },
      },
      allowContainerCreate:
        process.env.AZURE_STORAGE_ALLOW_CONTAINER_CREATE === 'true',
      baseURL: process.env.AZURE_STORAGE_ACCOUNT_BASEURL,
      connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
      containerName: process.env.AZURE_STORAGE_CONTAINER_NAME,
    }),
  ],
})

Configuration Options#azure-configuration

OptionDescriptionDefault
enabledWhether or not to enable the plugintrue
collectionsCollections to apply the Azure Blob adapter to
allowContainerCreateWhether or not to allow the container to be created if it does not existfalse
baseURLBase URL for the Azure Blob storage account
connectionStringAzure Blob storage connection string
containerNameAzure Blob storage container name
clientUploadsDo uploads directly on the client to bypass limits on Vercel.
useCompositePrefixesCombine collection prefix with document prefix instead of document prefix overriding it.false

Google Cloud Storage

@payloadcms/storage-gcs

Installation#gcs-installation

sh
pnpm add @payloadcms/storage-gcs

Usage#gcs-usage

  • Configure the collections object to specify which collections should use the Google Cloud Storage adapter. The slug must match one of your existing collection slugs.
  • When enabled, this package will automatically set disableLocalStorage to true for each collection.
  • When deploying to Vercel, server uploads are limited with 4.5MB. Set clientUploads to true to do uploads directly on the client. You must allow CORS PUT method for the bucket to your website.
ts
import { gcsStorage } from '@payloadcms/storage-gcs'
import { Media } from './collections/Media'
import { MediaWithPrefix } from './collections/MediaWithPrefix'

export default buildConfig({
  collections: [Media, MediaWithPrefix],
  plugins: [
    gcsStorage({
      collections: {
        media: true,
        'media-with-prefix': {
          prefix,
        },
      },
      bucket: process.env.GCS_BUCKET,
      options: {
        apiEndpoint: process.env.GCS_ENDPOINT,
        projectId: process.env.GCS_PROJECT_ID,
      },
    }),
  ],
})

Configuration Options#gcs-configuration

OptionDescriptionDefault
enabledWhether or not to enable the plugintrue
collectionsCollections to apply the storage to
bucketThe name of the bucket to use
optionsGoogle Cloud Storage client configuration. See Docs
aclAccess control list for files that are uploadedPrivate
clientUploadsDo uploads directly on the client to bypass limits on Vercel.
useCompositePrefixesCombine collection prefix with document prefix instead of document prefix overriding it.false

Uploadthing Storage

@payloadcms/storage-uploadthing

Installation#uploadthing-installation

sh
pnpm add @payloadcms/storage-uploadthing

Usage#uploadthing-usage

  • Configure the collections object to specify which collections should use uploadthing. The slug must match one of your existing collection slugs and be an upload type.
  • Get a token from Uploadthing and set it as token in the options object.
  • acl is optional and defaults to public-read.
  • When deploying to Vercel, server uploads are limited with 4.5MB. Set clientUploads to true to do uploads directly on the client.
ts
export default buildConfig({
  collections: [Media],
  plugins: [
    uploadthingStorage({
      collections: {
        media: true,
      },
      options: {
        token: process.env.UPLOADTHING_TOKEN,
        acl: 'public-read',
      },
    }),
  ],
})

Configuration Options#uploadthing-configuration

OptionDescriptionDefault
tokenToken from Uploadthing. Required.
aclAccess control list for files that are uploadedpublic-read
logLevelLog level for Uploadthinginfo
fetchCustom fetch functionfetch
defaultKeyTypeDefault key type for file operationsfileKey
clientUploadsDo uploads directly on the client to bypass limits on Vercel.

R2 Storage

<Banner type="warning"> **Note**: The R2 Storage Adapter is in **beta** as some aspects of it may change on any minor releases. </Banner>

@payloadcms/storage-r2

Use this adapter to store uploads in a Cloudflare R2 bucket via the Cloudflare Workers environment. If you're connecting to R2 from a Node.js environment (Vercel, Netlify, etc.) using the S3-compatible API, see Using with Cloudflare R2 (via S3 API) instead.

Installation#r2-installation

sh
pnpm add @payloadcms/storage-r2

Usage#r2-usage

  • Configure the collections object to specify which collections should use r2. The slug must match one of your existing collection slugs and be an upload type.
  • Pass in the R2 bucket binding to the bucket option, this should be done in the environment where Payload is running (e.g. Cloudflare Worker).
  • You can conditionally determine whether or not to enable the plugin with the enabled option.
ts
export default buildConfig({
  collections: [Media],
  plugins: [
    r2Storage({
      collections: {
        media: true,
      },
      bucket: cloudflare.env.R2,
    }),
  ],
})

Custom Storage Adapters

If you need to create a custom storage adapter, you can use the @payloadcms/plugin-cloud-storage package. This package is used internally by the storage adapters mentioned above.

Installation#custom-installation

pnpm add @payloadcms/plugin-cloud-storage

Usage#custom-usage

Reference any of the existing storage adapters for guidance on how this should be structured. Create an adapter following the GeneratedAdapter interface. Then, pass the adapter to the cloudStorage plugin.

ts
export interface GeneratedAdapter {
  /**
   * Additional fields to be injected into the base
   * collection and image sizes
   */
  fields?: Field[]
  /**
   * Generates the public URL for a file
   */
  generateURL?: GenerateURL
  handleDelete: HandleDelete
  handleUpload: HandleUpload
  name: string
  onInit?: () => void
  staticHandler: StaticHandler
}
ts
import { buildConfig } from 'payload'
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'

export default buildConfig({
  plugins: [
    cloudStoragePlugin({
      collections: {
        'my-collection-slug': {
          adapter: theAdapterToUse, // see docs for the adapter you want to use
        },
      },
    }),
  ],
  // The rest of your config goes here
})

Plugin options

This plugin is configurable to work across many different Payload collections. A * denotes that the property is required.

OptionTypeDescription
alwaysInsertFieldsbooleanWhen enabled, fields (like the prefix field) will always be inserted into the collection schema regardless of whether the plugin is enabled. This will be enabled by default in Payload v4. Default: false.
collections *Record<string, CollectionOptions>Object with keys set to the slug of collections you want to enable the plugin for, and values set to collection-specific options.
enabledbooleanTo conditionally enable/disable plugin. Default: true.

Collection-specific options

OptionTypeDescription
adapter *AdapterPass in the adapter that you'd like to use for this collection. You can also set this field to null for local development if you'd like to bypass cloud storage in certain scenarios and use local storage.
disableLocalStoragebooleanChoose to disable local storage on this collection. Defaults to true.
disablePayloadAccessControltrueSet to true to disable Payload's Access Control. More
generateFileURLGenerateFileURLOverride the generated file URL with one that you create.
prefixstringSet to media/images to upload files inside media/images folder in the bucket.

Prefix Composition

Storage adapters support two types of prefixes:

  • Collection prefix: Set at the adapter configuration level (e.g., prefix: 'media-folder')
  • Document prefix: Set per-document via the prefix field on the upload collection

By default, if a document has a prefix, it overrides the collection prefix entirely.

With useCompositePrefixes: true, the prefixes are combined:

# Without useCompositePrefixes (default)
Collection prefix: media-folder
Document prefix: user-123
Result: user-123/image.jpg

# With useCompositePrefixes: true
Collection prefix: media-folder
Document prefix: user-123
Result: media-folder/user-123/image.jpg

This is useful when you want a base folder structure (collection prefix) while still allowing per-document organization (document prefix).

ts
s3Storage({
  collections: {
    media: {
      prefix: 'uploads', // All files go under uploads/
    },
  },
  useCompositePrefixes: true, // Document prefixes append to collection prefix
  bucket: process.env.S3_BUCKET,
  // ...
})

Payload Access Control

Payload ships with Access Control that runs even on statically served files. The same read Access Control property on your upload-enabled collections is used, and it allows you to restrict who can request your uploaded files.

To preserve this feature, by default, this plugin keeps all file URLs exactly the same. Your file URLs won't be updated to point directly to your cloud storage source, as in that case, Payload's Access control will be completely bypassed and you would need public readability on your cloud-hosted files.

Instead, all uploads will still be reached from the default /collectionSlug/staticURL/filename path. This plugin will "pass through" all files that are hosted on your third-party cloud service—with the added benefit of keeping your existing Access Control in place.

If this does not apply to you (your upload collection has read: () => true or similar) you can disable this functionality by setting disablePayloadAccessControl to true. When this setting is in place, this plugin will update your file URLs to point directly to your cloud host. For a concrete example, see Using with Cloudflare R2 (via S3 API).

Conditionally Enabling/Disabling

The proper way to conditionally enable/disable this plugin is to use the enabled property.

ts
cloudStoragePlugin({
  enabled: process.env.MY_CONDITION === 'true',
  collections: {
    'my-collection-slug': {
      adapter: theAdapterToUse, // see docs for the adapter you want to use
    },
  },
}),