6.8 KiB
| title | aliases | tags | sources | created | updated | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Storage Adapters |
|
|
|
2026-05-15 | 2026-05-15 |
Storage Adapters
Payload ships with local disk storage by default. For production, swap to a cloud adapter — all adapters automatically set disableLocalStorage: true on the configured collections.
Available Adapters
| Service | Package |
|---|---|
| Vercel Blob | @payloadcms/storage-vercel-blob |
| AWS S3 | @payloadcms/storage-s3 |
| Azure Blob | @payloadcms/storage-azure |
| Google Cloud Storage | @payloadcms/storage-gcs |
| Uploadthing | @payloadcms/storage-uploadthing |
| Cloudflare R2 (Workers) | @payloadcms/storage-r2 |
Vercel Blob
import { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'
vercelBlobStorage({
enabled: true,
collections: {
media: true,
'media-with-prefix': { prefix: 'my-prefix' },
},
token: process.env.BLOB_READ_WRITE_TOKEN,
})
- Requires
BLOB_READ_WRITE_TOKEN(auto-set by Vercel after adding blob storage) - Vercel server upload limit: 4.5 MB — use
clientUploads: trueto bypass
| Option | Default |
|---|---|
enabled |
true |
addRandomSuffix |
false |
cacheControlMaxAge |
1 year |
clientUploads |
— |
useCompositePrefixes |
false |
AWS S3
import { s3Storage } from '@payloadcms/storage-s3'
s3Storage({
collections: { media: true },
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,
},
})
configaccepts anyS3ClientConfigsignedDownloads— use presigned URLs per-collection (useful for large files/videos)enabled: Boolean(process.env.S3_BUCKET)— skip plugin when credentials absent in local dev
Cloudflare R2 via S3 API
Use @payloadcms/storage-s3 for R2 from Node.js/Vercel/Netlify — not @payloadcms/storage-r2 (that's Cloudflare Workers only).
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', // required by R2
endpoint: process.env.R2_ENDPOINT, // S3 API endpoint, uploads only
forcePathStyle: true, // required for R2
},
})
Required env vars:
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 # R2.dev subdomain or custom domain
Warning: R2 buckets are private by default. The S3 API endpoint is for uploads only — you must enable the R2.dev subdomain or connect a custom domain to serve files publicly.
Azure Blob Storage
import { azureStorage } from '@payloadcms/storage-azure'
azureStorage({
collections: { media: true },
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,
})
Google Cloud Storage
import { gcsStorage } from '@payloadcms/storage-gcs'
gcsStorage({
collections: { media: true },
bucket: process.env.GCS_BUCKET,
options: {
apiEndpoint: process.env.GCS_ENDPOINT,
projectId: process.env.GCS_PROJECT_ID,
},
})
Uploadthing
uploadthingStorage({
collections: { media: true },
options: {
token: process.env.UPLOADTHING_TOKEN,
acl: 'public-read',
},
})
Cloudflare R2 (Workers native)
r2Storage({
collections: { media: true },
bucket: cloudflare.env.R2, // native R2 bucket binding
})
Beta. Use this only in Cloudflare Workers where R2 is available as a native binding. For all other Node.js environments, use
@payloadcms/storage-s3with the R2 S3-compatible endpoint.
Prefix Composition
By default, a document-level prefix overrides the collection prefix entirely.
With useCompositePrefixes: true, they are combined:
# Default
Collection prefix: media-folder, Document prefix: user-123
→ user-123/image.jpg
# useCompositePrefixes: true
→ media-folder/user-123/image.jpg
Payload Access Control
By default, Payload proxies all file requests through /<collectionSlug>/<staticURL>/<filename> so that access control rules apply even to cloud-hosted files. The file URL does not point directly to the cloud host.
Set disablePayloadAccessControl: true on a collection to bypass the proxy and serve files directly from the cloud host. Only safe when read: () => true or similar.
Custom Adapter
Use @payloadcms/plugin-cloud-storage as the base:
import { cloudStoragePlugin } from '@payloadcms/plugin-cloud-storage'
cloudStoragePlugin({
collections: {
'my-collection-slug': { adapter: myCustomAdapter },
},
})
Implement the GeneratedAdapter interface:
interface GeneratedAdapter {
fields?: Field[]
generateURL?: GenerateURL
handleDelete: HandleDelete
handleUpload: HandleUpload
name: string
onInit?: () => void
staticHandler: StaticHandler
}
Key Takeaways
- All official adapters auto-set
disableLocalStorage: trueon their collections - Vercel 4.5 MB limit — set
clientUploads: true+ allow CORS PUT on your bucket - R2 + Node.js → use
storage-s3withregion: 'auto',forcePathStyle: true,endpointfor uploads, separateR2_PUBLIC_URLfor serving useCompositePrefixes: truecombines collection + document prefixes instead of document overriding collection- Access control proxying is on by default — disable only when collection is fully public
- Conditionally enable with
enabled: Boolean(process.env.BUCKET)to skip in local dev
Related
- wiki/payloadcms/upload — upload collection config, imageSizes, focal point
- wiki/payloadcms/production-deployment — ephemeral vs persistent file storage, Dockerfile
- wiki/payloadcms/access-control — read access applied to static file serving
- wiki/payloadcms/plugin-form-builder — presigned URL pattern for form uploads
Sources
raw/upload__storage-adapters.md- https://payloadcms.com/docs/upload/storage-adapters