Payload CMS Storage Guide: S3, Cloudflare R2, MinIO & More
Learn how to configure Payload CMS storage adapters for AWS S3, Cloudflare R2, MinIO, GCS, and Vercel Blob with complete code examples.

Mastering Payload CMS Storage: From Local to Cloud integrations
When building with Payload CMS, managing where your media files live is a critical early decision. While Payload handles local storage beautifully out of the box, moving to production usually means integrating a dedicated cloud storage solution.
Payload's official plugins make this process relatively painless, but the documentation doesn't always show every specific vendor example in one place. In this post, we’re going to cover exactly how to configure Payload CMS for various storage providers including AWS S3, MinIO, Cloudflare R2, Google Cloud Storage, and Vercel Blob so you can find the exact setup you need.
1. The Default: Local Disk Storage
By default, if you don't install any cloud storage plugins, Payload CMS saves uploaded files directly to the local disk of your server.
To enable this, you simply set upload: true (or pass an options object) on any collection. Payload handles the rest.
1import { buildConfig } from 'payload/config'23export default buildConfig({4 collections: [5 {6 slug: 'media',7 upload: true, // Files are stored locally on the server8 fields: [],9 }10 ]11})
When to use this: Local development, testing, or small-scale applications where your server has persistent storage (not recommended for ephemeral serverless environments like Vercel).
2. AWS S3 Integration
Amazon S3 is the industry standard. Payload handles it via the @payloadcms/storage-s3 plugin.
For standard AWS S3, you need to pass your bucket name, region, and credentials. We can also optionally assign a prefix (which acts like a folder in your bucket) for specific collections.
1import { buildConfig } from 'payload/config'2import { s3Storage } from '@payloadcms/storage-s3'3import { Media } from './collections/Media'45export default buildConfig({6 collections: [Media],7 plugins: [8 s3Storage({9 collections: {10 // Simple boolean enables storage for the 'media' collection11 media: true,12 // Or configure specific options like a folder prefix13 'media-with-prefix': {14 prefix: 'my-payload-uploads',15 }16 },17 bucket: process.env.S3_BUCKET as string,18 config: {19 region: process.env.S3_REGION,20 credentials: {21 accessKeyId: process.env.S3_ACCESS_KEY_ID as string,22 secretAccessKey: process.env.S3_SECRET_ACCESS_KEY as string,23 },24 },25 }),26 ],27})
3. MinIO Integration (S3-Compatible)
MinIO is an excellent open-source, S3-compatible object storage server. Because it's S3-compatible, we use the exact same @payloadcms/storage-s3 plugin.
The Catch: MinIO requires you to pass forcePathStyle: true and a specific endpoint URL. Additionally, while MinIO doesn't actually care about the region, the underlying AWS SDK requires something to be there, so we pass a placeholder string.
1import { buildConfig } from 'payload/config'2import { s3Storage } from '@payloadcms/storage-s3'34export default buildConfig({5 plugins: [6 s3Storage({7 collections: {8 media: true,9 },10 bucket: process.env.MINIO_BUCKET as string,11 config: {12 endpoint: process.env.MINIO_ENDPOINT as string, // e.g., 'http://localhost:9000'13 forcePathStyle: true, // CRITICAL for MinIO14 region: 'placeholder', // Required by AWS SDK, but ignored by MinIO15 credentials: {16 accessKeyId: process.env.MINIO_ACCESS_KEY as string,17 secretAccessKey: process.env.MINIO_SECRET_KEY as string,18 },19 },20 }),21 ],22})
4. Cloudflare R2 Integration
Cloudflare R2 is another fantastic S3-compatible storage option with zero egress fees.
Because R2 usually serves files through a custom Cloudflare CDN domain rather than the raw bucket URL, we need to override how Payload generates the file URLs. We do this using generateFileURL inside the collection configuration.
1import { buildConfig } from 'payload/config'2import { s3Storage } from '@payloadcms/storage-s3'34export default buildConfig({5 plugins: [6 s3Storage({7 collections: {8 media: {9 // Disable Payload's default access control if serving publicly via CDN10 disablePayloadAccessControl: true,11 // Intercept and construct the public URL using your CDN domain12 generateFileURL: ({ filename, prefix }) => {13 const cdnDomain = process.env.PAYLOAD_PUBLIC_CLOUDFLARE_R2_URL || 'https://cdn.yourdomain.com'14 const pathPrefix = prefix ? `/${prefix}` : ''15 return `${cdnDomain}${pathPrefix}/${filename}`16 },17 }18 },19 bucket: process.env.R2_BUCKET as string,20 config: {21 endpoint: process.env.R2_ENDPOINT as string, // e.g., https://<account_id>.r2.cloudflarestorage.com22 region: 'auto', // Cloudflare uses 'auto'23 forcePathStyle: true,24 credentials: {25 accessKeyId: process.env.R2_ACCESS_KEY_ID as string,26 secretAccessKey: process.env.R2_SECRET_ACCESS_KEY as string,27 },28 },29 }),30 ],31})
5. Other Official Storage Plugins
If you aren't using an S3-compatible service, Payload officially maintains adapters for several other major cloud providers.
Google Cloud Storage (GCS)
Using the @payloadcms/storage-gcs plugin, you can easily connect to Google Cloud. Authentication is usually handled dynamically by your environment via the GOOGLE_APPLICATION_CREDENTIALS environment variable.
1import { buildConfig } from 'payload/config'2import { gcsStorage } from '@payloadcms/storage-gcs'34export default buildConfig({5 plugins: [6 gcsStorage({7 collections: {8 media: true,9 },10 bucket: process.env.GCS_BUCKET as string,11 options: {12 projectId: process.env.GCS_PROJECT_ID as string,13 },14 }),15 ],16})
Vercel Blob
If you are deploying on Vercel and want to use their native Blob storage, Payload has a dedicated @payloadcms/storage-vercel-blob plugin. This is arguably the simplest configuration, as it only requires a single token.
1import { buildConfig } from 'payload/config'2import { vercelBlobStorage } from '@payloadcms/storage-vercel-blob'34export default buildConfig({5 plugins: [6 vercelBlobStorage({7 collections: {8 media: true,9 },10 token: process.env.BLOB_READ_WRITE_TOKEN as string,11 }),12 ],13})
Final Thoughts
Whether you’re keeping things simple with local storage, scaling up with AWS, cutting egress costs with Cloudflare R2, or living in the Vercel ecosystem, Payload CMS makes connecting your media a breeze once you know the required parameters.
Make sure to always keep your keys secure in your .env file, and test your configuration in a staging environment before pushing your media collections to production!
👉 Contact the engineering team at Asynx Devs for a comprehensive system evaluation tailored to your business needs.
