Adds a dry-run mode to create operations on selected collections. When a flag such as _dryRun is passed in the query string, body or request context, the full create pipeline runs and the database transaction is then rolled back. The response returns the would-be document. It injects beforeOperation and afterOperation hooks and requires Payload 3 and Node.js 20 or newer. The author marks it as experimental.
Dry run plugin for Payload CMS
Installation
npm i @lizardglobal/payload-dry-run0
Stars
0
Forks
1
Open Issues
2 months ago
Last Updated
4
Weekly Downloads
v1.0.1
npm Version
35.4 KB
Unpacked Size
0
Dependencies
Trends appear once the first and last snapshots are 7 days apart. Collecting since Oct 22 — 7 daily snapshots so far.
Trends appear once the first and last snapshots are 7 days apart. Collecting since Oct 22 — 7 daily snapshots so far.
Each point is npm's rolling 7-day download count.
Health Score
Composite score based on GitHub activity, stars, npm downloads, publish recency, dependency count, and package size.
Badge for your README
[](https://payloaddirectory.dev/plugins/1269916906-root)<a href="https://payloaddirectory.dev/plugins/1269916906-root"><img src="https://payloaddirectory.dev/badge/1269916906-root.svg" alt="Payload Directory"></a>README
@lizardglobal/payload-dry-runPayload CMS plugin that allows you to test your create operations without persisting data — full validation, hooks, and business logic, automatically rolled back.
[!WARNING] This plugin is still experimental. APIs, collection schemas, and behavior may change without a stable compatibility guarantee. Use in production with caution and pin versions deliberately.
disabled flag for environment-based opt-out.^3.0.0>=20pnpm add @lizardglobal/payload-dry-run
# or
npm install @lizardglobal/payload-dry-run
# or
yarn add @lizardglobal/payload-dry-run
import { dryRunCreatePlugin } from '@lizardglobal/payload-dry-run'
import { buildConfig } from 'payload'
export default buildConfig({
// ...
plugins: [
dryRunCreatePlugin({
collections: ['users', 'orders'],
}),
],
})
To test a create without persisting, pass _dryRun=true anywhere Payload reads it — query string, request body, or context:
POST /api/orders?_dryRun=true
The full operation runs. Validation, hooks, access control — everything executes. The created document is then rolled back. The response is the would-be result, as if the create had committed.
| Option | Type | Default | Description |
|---|---|---|---|
collections | string[] | required | Slugs of the collections to enable dry-run creates on |
dryRunFieldName | string | { _default?: string; [slug]: string } | '_dryRun' | Field name(s) used to trigger the dry run (details) |
disabled | boolean | false | Set true to skip the plugin entirely (e.g. in production) |
dryRunFieldName controls what query param / body key / context key triggers the dry run. Three forms are supported:
A single string — same field name for every collection (default behavior):
dryRunCreatePlugin({
collections: ['users', 'orders'],
dryRunFieldName: '_dryRun',
})
An object — per-collection field names:
dryRunCreatePlugin({
collections: ['users', 'orders'],
dryRunFieldName: {
users: '_dryRunUser',
orders: '_dryRunOrder',
},
})
An object with _default — per-collection overrides with a fallback for anything not listed:
dryRunCreatePlugin({
collections: ['users', 'orders', 'products'],
dryRunFieldName: {
users: '_dryRunUser',
_default: '_dryRun', // used for 'orders' and 'products'
},
})
If a collection is listed in collections but has no entry in the object and no _default, the plugin falls back to '_dryRun'.
The plugin reads the dry-run flag from several places. Any one of these is sufficient:
| Source | Example |
|---|---|
| Query string | POST /api/orders?_dryRun=true |
| Request body | { ..., "_dryRun": true } |
| Request context | req.context._dryRun = true |
| Search params | searchParams.get('_dryRun') |
Accepted truthy values: true, 1, "1", "on", "true", "yes" (case-insensitive). Everything else is treated as falsy.
When the flag is detected, req.context._dryRun is set to true for the remainder of the request lifecycle — subsequent hooks can read it too.
withCreateDryRunApplies the dry-run behavior directly to a single CollectionConfig, without going through the plugin. Use this when you're building your own plugin or composing collection configs manually:
import { withCreateDryRun } from '@lizardglobal/payload-plugin-dry-run'
const Orders: CollectionConfig = withCreateDryRun(
{
slug: 'orders',
fields: [...],
},
'_dryRun', // optional, defaults to '_dryRun'
)
withCreateDryRun is idempotent — calling it twice on the same config object is a no-op.
APIErrorA thin wrapper around Payload's built-in APIError. Exposed for use in hooks or custom endpoints that need to throw a formatted Payload error:
import { APIError } from '@lizardglobal/payload-plugin-dry-run'
throw new APIError('Something went wrong', 422)
The second argument is the HTTP status code (defaults to 400). The error is always marked as public.
The plugin injects two hooks on each configured collection:
beforeOperation — detects the dry-run flag and, if the collection uses auth with email verification, sets disableVerificationEmail: true so no email is sent during validation.
afterOperation — after a successful create, checks whether this was a dry-run request. If it was, it calls req.payload.db.rollbackTransaction(req.transactionID) to undo the write. The already-computed result is returned as-is, so the caller receives the full would-be document.
If no transaction ID is present when a rollback is needed, the plugin throws rather than silently leaving behind a committed document.
A hidden, read-only, virtual checkbox field (_dryRun by default) is added to each collection. This field is never persisted — it exists so Payload's own input handling recognises the key in submitted data.
Set DEBUG=true in your environment to enable verbose logging:
DEBUG=true pnpm dev
The plugin will log each hook invocation, the extracted result ID, the transaction ID, and whether a rollback was performed — all prefixed with [DRY RUN].
payload plugin tooling
Shared lint, format, TypeScript, build, test and CI config for developing Payload plugins
7339/wk0v4
payload cli
Command-line access to Payload collections and globals through the Local API, built for AI agents and humans
616/wk13v3
payload integration test utils
Vitest helpers that give integration tests a ready Payload Local API instance backed by a containerized database
436/wk0v3
payload plugin template
Starter template for building Payload v3 plugins with a dev app, tests and Docker support
197v3
payload utilities
Developer utilities for traversing, flattening and transforming Payload documents and tidying generated types
6828/wk15v3
payload dev tools
Floating dev toolbar, /dev pages and an app snapshot for AI agents in Payload projects on Next.js
6214/wk18v3