Payload Plugins
Pluginspayload-images

payload-images

Upload a picture once, then request any size, crop, or format from a URL — rendered on demand and cached forever.

For AI / LLMs: View Markdown

@pro-laico/payload-images optimizes images on demand. Upload a picture once, then ask for any size, crop, or format with a URL. The plugin renders each variant the first time it's requested, then caches it forever.

It's the Sanity-style image pipeline for Payload: art-directed focal cropping, AVIF/WebP negotiation, instant placeholders, and a durable cache — all driven from your CMS. Unlike Payload's built-in imageSizes, you never pre-declare a fixed set of sizes; any size is available the moment a layout asks for it.

pnpm add @pro-laico/payload-images

Requirements

Next.js 15+, plus Payload ^3 and React 19. This plugin isn't framework-agnostic and the next peer isn't optional: the transform endpoint, prewarming, and preset generation all persist their work with Next's after() (next/server), which is what lets a request return the bytes and store the variant afterwards. Without it there's no way to cache a generated variant off the response path — the endpoint is the plugin.

What's included

  • Never bigger than it needs to be: every layout requests the exact size, crop, and format it needs; the endpoint renders it once on demand, then caches it forever.
  • Focal-point cropping, subject-aware by default: uploads get a saliency-detected focal point when the analysis finds a subject and the editor hasn't set one; refine it once and every crop, at any ratio, stays centered on it.
  • Modern formats, negotiated: AVIF / WebP served to browsers that accept them, with a graceful fallback.
  • One component, one helper: imageFor fetches a render-ready doc, <ResponsiveImage> paints it — srcset, placeholder, and crop included, zero client JS.
  • Instant placeholders, cropped like the image: a ladder of quality tiers stored at upload; every read gets a finished placeholder focal-cropped to the exact ratio being rendered — sm by default, .blur(tier) to pick, blur: false to opt out.
  • Self-busting cache, smart prewarming, instant fallbacks: URLs refresh themselves on change, prewarming generates what your site actually serves before anyone asks, and a cold request serves a nearby stand-in instantly.

Sample images rendered in the frontend by the images-sandbox example repoSample images rendered in the frontend by the images-sandbox example repo

Quickstart

Add the plugin

This registers an images upload collection with the transform pipeline attached.

import { buildConfig } from 'payload'
import sharp from 'sharp'
import { imagesPlugin } from '@pro-laico/payload-images'

const serverURL = process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:3000'

export default buildConfig({
  sharp, // required: the endpoint resizes/crops with Sharp
  serverURL, // used to read originals from cloud/relative storage
  plugins: [imagesPlugin()],
})

Want it called media? Pass imagesPlugin({ collections: { images: { slug: 'media' } } }) to rename the collection the plugin registers — and delete your own, since this one is a full upload collection. Add your fields in the same override. See Renaming a collection.

Keep Sharp out of the bundle

Sharp ships a native binary that breaks when bundled, so tell Next not to (Turbopack and webpack both need this):

// next.config.ts
const nextConfig = { serverExternalPackages: ['sharp'] }
export default nextConfig

Generate the admin import map

collections.images.options.focalUI is on by default, so the plugin registers custom admin components — the focal-point picker and the Preset manager panel. Regenerate the import map so they load:

pnpm payload generate:importmap

Upload an image

Start the app, open the images collection in the admin, and upload a picture. Copy its document id — the next step renders from it.

Render your first image

// src/lib/imageFor.ts — seed the getter once
import config from '@payload-config'
import { getPayload } from 'payload'
import { createImageFor } from '@pro-laico/payload-images'

export const imageFor = createImageFor(getPayload({ config }))
// any server component
import { ResponsiveImage } from '@pro-laico/payload-images/components/image'
import { imageFor } from '@/lib/imageFor'

const img = await imageFor(id).aspectRatio('16:9').fetch()
return img && <ResponsiveImage {...img} sizes="(max-width: 768px) 100vw, 50vw" />

That's a focal-cropped, responsive, placeholder-backed <img>. Rendering images covers everything it just did.

Explore

Plugin options

Zero-config by default — every option has a sensible default. See Reference for options, environment variables, CLI commands, endpoints, and exports.

On this page