Payload Image Cropper

Setup

S3 & Cloud Storage

Use this when media is stored with @payloadcms/storage-s3 (AWS S3, DigitalOcean Spaces, MinIO, etc.).

  1. 1

    Install S3 storage adapter

    Terminal
    pnpm add @payloadcms/storage-s3
  2. 2

    Configure environment variables

    Example .env:

    Terminal
    S3_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. 3

    Add s3Storage to Payload plugins

    typescript
    import { 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. 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.

    typescript
    import { 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. 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.