npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lizardglobal/payload-dry-run

v1.0.1

Published

Dry run plugin for Payload CMS

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-plugin-dry-run-create
# or
npm install @lizardglobal/payload-plugin-dry-run-create
# or
yarn add @lizardglobal/payload-plugin-dry-run-create

Quick Start

import { dryRunCreatePlugin } from '@lizardglobal/payload-plugin-dry-run-create'
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

| 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) |

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:

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

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

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

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