Setup
S3 & Cloud Storage
Use this when media is stored with @payloadcms/storage-s3 (AWS S3, DigitalOcean Spaces, MinIO, etc.).
- 1
Install S3 storage adapter
Terminalpnpm add @payloadcms/storage-s3 - 2
Configure environment variables
Example .env:
TerminalS3_BUCKET=your-bucket S3_REGION=us-east-1 S3_ACCESS_KEY=your-access-key S3_SECRET_KEY=your-secret-key S3_ENDPOINT= # omit for AWS; required for DO Spaces / MinIO S3_PREFIX=media CDN_ENDPOINT=https://cdn.example.com - 3
Add s3Storage to Payload plugins
typescriptimport { s3Storage } from '@payloadcms/storage-s3' s3Storage({ acl: 'public-read', bucket: process.env.S3_BUCKET, collections: { media: { generateFileURL: ({ filename, prefix }) => { return [process.env.CDN_ENDPOINT, prefix, filename].filter(Boolean).join('/') }, prefix: process.env.S3_PREFIX, }, }, config: { credentials: { accessKeyId: process.env.S3_ACCESS_KEY, secretAccessKey: process.env.S3_SECRET_KEY, }, endpoint: process.env.S3_ENDPOINT, forcePathStyle: false, region: process.env.S3_REGION, }, }) - 4
Add the crop plugin with matching S3 config
Copy the same bucket, config, acl, and prefix from s3Storage. The plugin uploads crops on save and deletes them when source media is removed.
typescriptimport { cropImagePlugin } from 'payload-plugin-image-cropper' cropImagePlugin({ mediaCollectionSlug: 'media', mediaDir: path.join(process.cwd(), 'public/media'), s3: { acl: 'public-read', bucket: process.env.S3_BUCKET, config: { credentials: { accessKeyId: process.env.S3_ACCESS_KEY, secretAccessKey: process.env.S3_SECRET_KEY, }, endpoint: process.env.S3_ENDPOINT, forcePathStyle: false, region: process.env.S3_REGION, }, prefix: process.env.S3_PREFIX, generateUrl: ({ filename, prefix }) => { return [process.env.CDN_ENDPOINT, prefix, filename].filter(Boolean).join('/') }, }, }) - 5
disableLocalStorage: true (optional)
If you disable local storage entirely, the plugin fetches the source image from its URL automatically. In that case mediaDir is unused for reading source files, but you may still pass it for consistency.
generateUrl uses the same logic as generateFileURL in s3Storage.
What happens on save and delete
- On save: Each crop is uploaded via PutObject to your bucket.
- On media delete: All associated crop files are removed via ListObjects + DeleteObject.