Use Cloudflare R2 with Payload CMS
Written by Bridger Tower
This guide demonstrates how to integrate Cloudflare R2 with Payload CMS for efficient media storage. Cloudflare R2 offers an S3-compatible storage solution with significant cost advantages, particularly its zero egress fees policy.
Why Choose R2 with Payload CMS?
- Cost Efficiency: Zero egress fees, unlike traditional S3 providers
- Global Performance: Leverage Cloudflare's global edge network
- Native Integration: Full S3 compatibility with Payload CMS
- Simplified Management: Easy-to-use dashboard and API
- Predictable Pricing: Pay only for storage used, not for bandwidth
Prerequisites
- Cloudflare account with R2 access enabled
- Payload CMS project (version 3.x or later)
- Node.js 16.x or later
- Package manager (npm, yarn, or pnpm)
Implementation Guide
1. Install Required Dependencies
bash
# Using npm npm install @payloadcms/storage-s3 # Using yarn yarn add @payloadcms/storage-s3 # Using pnpm pnpm add @payloadcms/storage-s3
2. Configure R2 in Cloudflare
- Navigate to Cloudflare Dashboard
- Select "R2" from the sidebar
- Create a new bucket:
- Click "Create bucket"
- Enter a unique bucket name
- Choose your preferred region
- Generate API credentials:
- Go to "R2 > Manage R2 API Tokens"
- Click "Create API Token"
- Select "Admin Read & Write" permissions
- Save your credentials securely
3. Set Up Environment Variables
Create or update your .env file:
bash
# .env R2_ACCESS_KEY_ID=your_access_key_id R2_SECRET_ACCESS_KEY=your_secret_access_key R2_BUCKET=your_bucket_name R2_ENDPOINT=https://<account_id>.r2.cloudflarestorage.com
4. Configure Payload CMS
Update your payload.config.ts:
typescript
import { buildConfig } from 'payload/config'
import { s3Storage } from '@payloadcms/storage-s3'
export default buildConfig({
plugins: [
s3Storage({
collections: {
media: {
adapter: 's3',
disableLocalStorage: true, // Optional: disable local storage
prefix: 'media', // Optional: prefix all files with this path
},
},
bucket: process.env.R2_BUCKET!,
config: {
endpoint: process.env.R2_ENDPOINT!,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
},
region: 'auto',
forcePathStyle: true,
},
}),
],
})5. Media Collection Configuration
Configure your media collection to work with R2:
typescript
import { CollectionConfig } from 'payload/types'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
staticURL: '/media',
staticDir: 'media',
mimeTypes: ['image/*', 'application/pdf'], // Customize as needed
imageSizes: [
{
name: 'thumbnail',
width: 400,
height: 300,
position: 'centre',
},
{
name: 'card',
width: 768,
height: 1024,
position: 'centre',
},
],
},
fields: [], // Add custom fields as needed
}Advanced Configuration
Custom Upload Handlers
typescript
s3Storage({
// ...other config
beforeUpload: async ({ req, file }) => {
// Customize file before upload
return file
},
afterUpload: async ({ req, file, collection }) => {
// Perform actions after successful upload
console.log(`File ${file.filename} uploaded to ${collection.slug}`)
},
})Error Handling
Implement robust error handling:
typescript
try {
await payload.create({
collection: 'media',
data: {
// your upload data
},
})
} catch (error) {
if (error.code === 'AccessDenied') {
console.error('R2 access denied - check credentials')
} else if (error.code === 'NoSuchBucket') {
console.error('R2 bucket not found')
} else {
console.error('Upload failed:', error)
}
}Troubleshooting Guide
Common Issues
- Connection Errors
- Verify R2 credentials are correct
- Confirm endpoint URL format
- Check network connectivity
- Validate IP allowlist settings
- Upload Failures
- Verify bucket exists and is accessible
- Check API token permissions
- Confirm file size limits
- Validate MIME type restrictions
- URL Generation Issues
- Verify public bucket configuration
- Check custom domain settings
- Validate URL formatting
Health Check
Run this diagnostic code to verify your setup:
typescript
async function checkR2Setup() {
try {
const testUpload = await payload.create({
collection: 'media',
data: {
// test file data
},
})
console.log('R2 connection successful:', testUpload)
} catch (error) {
console.error('R2 setup check failed:', error)
}
}Performance Optimization
File Compression
- Implement image compression before upload
- Use appropriate file formats
- Set reasonable size limits
Caching Strategy
- Configure browser caching headers
- Implement cache-control policies
- Use Cloudflare's caching features
Security Best Practices
Access Control
- Use least-privilege access tokens
- Implement bucket policies
- Enable audit logging
Data Protection
- Enable encryption at rest
- Implement secure file validation
- Regular security audits
Resources
- Payload CMS Documentation
- Cloudflare R2 Documentation
- S3 Plugin Documentation
- Cloudflare Workers Documentation
Support
For additional help:
- Join the Payload CMS Discord
- Visit the Cloudflare Community Forums
- Submit issues on GitHub