payload-watermark-plugin
v0.3.0
Published
Conditional image watermarking for Payload CMS 3.x based on folder assignment
Readme
payload-watermark-plugin
Conditional image watermarking for Payload CMS 3.x based on folder assignment.
Requirements
- Payload CMS
^3.0.0 - Node.js
>=18.0.0 - sharp
>=0.31.0— must be installed as a direct dependency in your consuming project - The Folders feature enabled in your Payload config (
folders: trueor a custom folders config) - Watermark images stored in a publicly accessible location (R2/S3 public bucket, CDN, etc.)
Installation
pnpm add payload-watermark-plugin sharp
sharpis listed as a peer dependency — you must install it explicitly in your project.
Configuration
1. Add the plugin to your payload.config.ts
import { watermarkPlugin } from 'payload-watermark-plugin'
import { buildConfig } from 'payload'
export default buildConfig({
// ...
plugins: [
watermarkPlugin({
collections: ['media'], // collections to enable watermarking for
}),
],
})2. Run the migration
After adding the plugin, create and apply the database migration for the watermark_settings global:
pnpm payload migrate:create
pnpm payload migrate3. Regenerate types
pnpm generate:types
pnpm generate:importmapPlugin Options
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| collections | string[] | Yes | Slugs of collections to apply watermarking to. At least one required. The first entry is also used as the relationship target for the watermark image field. |
Admin Usage
After installation, a Watermark Settings global appears in the admin panel under the Settings group.
| Field | Description | |-------|-------------| | Enable Watermarking | Master switch. Turn off to disable all watermarking without changing other settings. | | Watermark Image | Select an image from your media library to use as the watermark. Must be a PNG for best results. Must be publicly accessible via URL. | | Enabled Folders | Select one or more folders. Only uploads assigned to these folders will be watermarked. | | Position | Where to place the watermark on the image (9 positions: top/center/bottom × left/center/right). Default: Bottom Right. | | Size (% of image width) | Watermark width as a percentage of the main image width. Range: 1–100. Default: 20. | | Opacity | Watermark transparency. Range: 0 (invisible) to 1 (fully opaque). Default: 0.8. |
Fields 2–6 are hidden when Enable Watermarking is unchecked.
How It Works
Hook timing
The plugin registers a beforeOperation hook on each configured collection. This hook fires before generateFileData, which means:
- The watermark is applied to
req.file.data(the raw image buffer) - All generated image sizes (thumbnails, etc.) will also carry the watermark
- The original uploaded file gets the watermark baked in
Folder check
On each upload, the hook reads args.data.folder. If the assigned folder is in the enabledFolders list in the Global, watermarking proceeds. Otherwise the upload is passed through unchanged.
Watermark processing (sharp)
- Fetches the watermark image from its public URL
- Resizes it to the configured percentage of the main image width
- Applies opacity via a raw RGBA pixel loop (multiplies alpha channel by the opacity factor — preserves colour accuracy)
- Composites the watermark onto the main image using sharp's
composite()withblend: 'over'
Error handling
- If the
watermark-settingsglobal is not yet migrated (table missing), the hook logs a warning and passes the upload through unchanged - If watermark processing fails for any reason, the error is logged and the original upload proceeds — uploads are never blocked by watermark failures
Notes
- Public watermark URL required: The watermark image must be reachable via HTTP from the server at upload time. If your storage bucket is private, either make the watermark image public or configure a pre-signed URL strategy separately.
- PNG watermarks: Use PNG files with transparency for best results. JPEG watermarks will work but cannot have transparent backgrounds.
- tempFilePath mode: If Payload is configured to stream uploads to temp files instead of memory (
useTempFiles: trueinexpress-fileupload), watermarking is skipped with a warning. The plugin requiresreq.file.datato be an in-memory Buffer.
License
CC-BY-SA-4.0 — Creative Commons Attribution-ShareAlike 4.0 International
