LizardGlobalGH

payload dry run

by LizardGlobalGH
Testing & developer experience
v3
^3.84.1
MIT

Overview

Summary generated from the README

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.

Features

  • Runs validation, access control and hooks on create, then rolls back the transaction
  • Trigger via query string, request body, request context or search params
  • Per-collection or shared dry-run field name configuration
  • Disables verification emails for auth collections during dry-run creates
  • Disabled flag for environment-based opt-out
  • Low-level helper to apply dry-run behavior to a single collection config

Use cases

  • Check whether a create request would pass validation and hooks before submitting it for real
  • Preview the resulting document of an order or user creation without leaving data behind
  • Validate form or import payloads against business logic in staging environments

From the author

Dry run plugin for Payload CMS

Installation

npm i @lizardglobal/payload-dry-run

0

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

GitHub stars

Trends appear once the first and last snapshots are 7 days apart. Collecting since Oct 22 — 7 daily snapshots so far.

npm weekly downloads

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

46/100Fair

Composite score based on GitHub activity, stars, npm downloads, publish recency, dependency count, and package size.

Badge for your README

Payload Directory health badge
Markdown
[![Payload Directory](https://payloaddirectory.dev/badge/1269916906-root.svg)](https://payloaddirectory.dev/plugins/1269916906-root)
HTML
<a href="https://payloaddirectory.dev/plugins/1269916906-root"><img src="https://payloaddirectory.dev/badge/1269916906-root.svg" alt="Payload Directory"></a>
Updated Aug 3, 2026Created Jun 15, 2026Published Jul 28, 2026

README

@lizardglobal/payload-dry-run

Payload CMS plugin that allows you to test your create operations without persisting data — full validation, hooks, and business logic, automatically rolled back.

npm

Release

[!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.

Features

  • Validate create requests end-to-end without writing to the database.
  • Full hook and validation pipeline runs — only the final commit is rolled back.
  • Per-collection field names, or a single shared name for all collections.
  • Disables verification emails automatically for auth collections on dry-run creates.
  • Zero-overhead when not triggered — no impact on normal create operations.
  • disabled flag for environment-based opt-out.

Table of Contents

Requirements

  • Payload ^3.0.0
  • Node.js >=20

Installation

pnpm add @lizardglobal/payload-dry-run
# or
npm install @lizardglobal/payload-dry-run
# or
yarn add @lizardglobal/payload-dry-run

Quick Start

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.

Configuration

Plugin Options

OptionTypeDefaultDescription
collectionsstring[]requiredSlugs of the collections to enable dry-run creates on
dryRunFieldNamestring | { _default?: string; [slug]: string }'_dryRun'Field name(s) used to trigger the dry run (details)
disabledbooleanfalseSet true to skip the plugin entirely (e.g. in production)

Field Name Resolution

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'.

Triggering a Dry Run

The plugin reads the dry-run flag from several places. Any one of these is sufficient:

SourceExample
Query stringPOST /api/orders?_dryRun=true
Request body{ ..., "_dryRun": true }
Request contextreq.context._dryRun = true
Search paramssearchParams.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.

Low-Level API

withCreateDryRun

Applies 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.

APIError

A 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.

How It Works

The plugin injects two hooks on each configured collection:

  1. 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.

  2. 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.

Debugging

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].

Similar plugins