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

@agentmarketing/payload-site-forms

v1.2.0

Published

Payload 3 plugin: Site Forms, submissions, form sequences (draft/save progress), and mailing. CLI (init) scaffolds UI, Site Form block, and editable mail-templates into your repo.

Readme

Payload Site Forms

A full Site Forms layer for Payload 3: reusable form definitions in the admin, submissions in the database, optional multi-step sequences with saved progress and staff approval, a front-end block, and mailing hooks — delivered without touching Payload’s official plugin-form-builder (they can coexist for legacy sites).

By Shane Farmer / Agent Marketing
Questions or issues? Email [email protected].

The model at a glance

Every Site Form document is a sequence of steps. A top-level Multi Form checkbox in the admin controls whether the sequence may contain more than one step.

| Multi Form | Meaning | | --- | --- | | Off (default) | Exactly one step. The step itself can be either a single page of fields or a multi-page flow. Submits in one go via POST /api/site-form-submissions. No drafts. | | On | Two or more steps (rendered as tabs). Each step is independent and may itself be multi-page. Supports Draft safe answers, staff approval gates per step, identifier-based resume, and pipeline runs. |

Each step has its own stepType:

  • Single page (standard) — a flat list of fields.
  • Multi-page (multi_step) — pages of fields with Next / Previous / Submit.

That means from the editor’s point of view there is just one form type (a sequence), driven by two checkboxes (Multi Form on the document, Step layout on each step). From the runtime point of view there is one renderer (SiteFormBlock), fed by a single normalized shape.

How the data stays backwards-compatible

Older databases may contain documents with formType: 'standard', formType: 'multi_step' (legacy flat fields / stages fields), or formType: 'multi_form' with a relationship-based pipeline array. All of these:

  • Remain fully editable in the admin — legacy fields are shown only when the document is already legacy.
  • Render at runtime via an adapter (normalizeSiteForm) that projects them into the same step sequence shape the new model uses.

No migration is required to use the plugin on an existing dataset.

How it fits a Payload app

  • You register siteFormsPlugin(...) in your Payload plugins array. The plugin merges collections (site-forms, site-form-submissions, site-form-draft-submissions), fields, hooks, and endpoints into your config.
  • Editors manage forms under Site Forms in the admin. Submissions are stored under Site Form Submissions; sequence progress lives on Site Form Draft Submissions.
  • You expose the Site Form layout block on your pages (and/or posts). The block’s front-end renderer (SiteFormBlock) reads the document, normalises it, and dispatches to either the single-step renderer or the multi-step sequence pipeline — all inside one file.
  • The payload-site-forms CLI is the supported way to wire a new app. payload-site-forms init copies the UI helpers, the block, the mail templates, and patches src/plugins/index.ts, next.config.js, src/collections/Pages, and src/blocks/RenderBlocks.tsx.

Payload 3, Next.js App Router, and @payloadcms/richtext-lexical are assumed.

Install

pnpm add @agentmarketing/payload-site-forms
# or: npm install @agentmarketing/payload-site-forms

Bumping to a newer release:

pnpm update @agentmarketing/payload-site-forms
# or pin a major: edit package.json to "^1.x" and run pnpm install

After any update, run pnpm payload generate:types so your TypeScript collection types match the plugin schema.

Quick setup

1. Scaffold

From the project root:

pnpm exec payload-site-forms init
# or: npx payload-site-forms init

This copies:

  • src/payload-site-forms/* — field registry and React helpers (React Hook Form integration, identifier helpers, draft hook, submission serializers).
  • src/blocks/SiteForm/* — the Site Form layout block (slug siteFormBlock, relates to site-forms) and its single-file renderer Component.tsx.
  • src/components/ui/* — shadcn-flavour primitives the scaffold imports: button, tabs, radio-group, checkbox, label, input, textarea, select. Already have a shadcn install? init skips any primitive that already exists in your project.
  • src/components/Recaptcha.tsx — a self-contained reCAPTCHA v3 wrapper. No-ops when NEXT_PUBLIC_RECAPTCHA_KEY is not set, so safe to keep without a reCAPTCHA account.
  • src/lib/utils.ts — the cn() helper used by the primitives.
  • mail-templates/site-forms/* — editable HTML / TXT copies of the bundled mail templates (commit these in your repo).

After init you'll see a one-liner command to install the Radix + CVA peers that the primitives need:

pnpm add @radix-ui/react-checkbox @radix-ui/react-label @radix-ui/react-radio-group \
  @radix-ui/react-select @radix-ui/react-slot @radix-ui/react-tabs \
  class-variance-authority clsx lucide-react tailwind-merge

Re-running is safe — pnpm add is a no-op for packages you already have.

…and patches:

  • src/plugins/index.ts — registers siteFormsPlugin({ mailTemplatesPath: ... }).
  • next.config.js — adds the package to transpilePackages.
  • src/collections/Pages/index.ts — registers SiteFormBlock alongside the default FormBlock.
  • src/blocks/RenderBlocks.tsx — maps the siteFormBlock slug to SiteFormBlock.

Existing projects upgrading from a pre-1.0 scaffold: init also removes the now-obsolete src/blocks/SiteForm/MultiFormPipeline.tsx (backed up to .bak) because the single-step and multi-step renderers are consolidated into Component.tsx.

If you prefer to register the plugin by hand, the minimal wiring is:

import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { siteFormsPlugin } from '@agentmarketing/payload-site-forms'

const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')

export const plugins = [
  siteFormsPlugin({
    mailTemplatesPath: path.join(projectRoot, 'mail-templates', 'site-forms'),
  }),
]

2. Regenerate Payload artifacts

pnpm payload generate:types
pnpm payload generate:importmap

3. Create a form and add it to a page

In the admin: Site Forms → New. Author your step(s), tick Multi Form if you need more than one step, fill the Confirmation tab and Mailing tab, then add the Site Form block to a page or post and select the form.

The editor experience

Content tab

  • Title / Slug — standard.
  • Multi Form — unticked by default. Turn on to author a sequence.
  • Draft safe answers (Multi Form only) — enable visitor draft saving for this sequence. Requires at least one identifier field on one of the steps.
  • When to save answers (Draft safe answers only)
    • On submit (default) — the visitor uses a "Save draft" button.
    • After each page — snapshot after every step / page navigation.
    • When they leave each field — snapshot on blur.
  • Steps — array of steps in the order visitors see them.
    • Step label (Multi Form only) — shown as a tab heading.
    • Step layoutSingle page or Multi-page.
    • Fields (single-page steps) or Pages (multi-page steps).
    • Visitor must finish this step before the next (Multi Form only).
    • Needs staff approval before continuing (Multi Form only) — blocks progression until an admin approves the step.

When Multi Form is off, the editor UI collapses to a single-form experience:

  • The "Steps" label and the Add Step button are hidden — there is always exactly one step.
  • The per-step Step label field is hidden.
  • The Identifier checkbox on each field block is hidden (identifiers are only meaningful for sequences with draft saving).

Tick Multi Form to reveal all of the above and author a full sequence.

Confirmation tab

  • Confirmation type — message or redirect.
  • Confirmation message — Lexical rich text shown after successful submission.
  • Redirect URL — used when confirmation type is redirect.
  • Submit button label.
  • Sequence awaiting-approval message (Multi Form only) — displayed while a step is pending staff review.
  • Sequence rejected message (Multi Form only) — displayed when a step is rejected.

Mailing tab

Recipients, subjects, and Lexical body fields for each mail type (submission, approval request, approval approved, approval rejected). Bodies populate the {{submissionMessage}} / {{submissionMessagePlain}} merge tags inside the HTML / TXT templates.

The front-end renderer

SiteFormBlock is a single consolidated React component exposed from src/blocks/SiteForm/Component.tsx. It:

  1. Reads the Site Form document from the block props.
  2. Runs it through normalizeSiteForm(doc) which returns a NormalizedSiteForm (unified steps array plus policy-corrected flags such as isSingleStep, allowPreSave, saveStrategy).
  3. Dispatches to one of two internal components in the same file:
    • Single-step path — used for any isSingleStep result (new Multi Form-off docs, legacy standard, legacy multi_step). Submits directly to /api/site-form-submissions. No pipeline runs, no draft saves.
    • Multi-step sequence path — used for any sequence with more than one step. Drives the tab strip, pipeline runs, identifier gate, per-step approval, and draft progress.

There is no second renderer file to keep in sync. If you are adjusting layout, styling, or submit behaviour, you only ever edit Component.tsx.

Consuming the adapter yourself

If you build your own block, you can use the adapter directly:

import {
  normalizeSiteForm,
  type NormalizedSiteForm,
} from '@agentmarketing/payload-site-forms/normalize-site-form'

const form: NormalizedSiteForm = normalizeSiteForm(doc)

if (form.isSingleStep) {
  // render form.steps[0]
} else {
  // render form.steps as a sequence
}

Client-only subpaths

The main package entry pulls in server-oriented code. For client components (or a thin src/payload-site-forms/index.ts re-export) import the subpaths:

import {
  resolveInlineSteps,
  type InlineSequenceStep,
} from '@agentmarketing/payload-site-forms/resolve-sequence-steps'

import {
  normalizeSiteForm,
  type NormalizedSiteForm,
  type NormalizedStep,
  type SiteFormLike,
} from '@agentmarketing/payload-site-forms/normalize-site-form'

siteFormsPlugin, createDefaultFieldBlocks, and the mailing helpers are server only.

Submissions and sequences

  • site-form-submissions — one row per completed submission. Single-step forms write directly here. Sequences write the final consolidated submission here when all steps are complete.
  • site-form-draft-submissions — one row per in-progress sequence session. Holds the pipeline run, per-step snapshots, approval status, and the identifier fingerprint used by the resume lookup.
  • Endpoints exposed by the plugin:
    • POST /api/site-form-draft-submissions/multi-form-actionensure_run, save_progress, submit_stage for sequences.
    • POST /api/site-form-submissions/resume-lookup — identifier-based resume.
    • POST /api/site-form-draft-submissions/draft-upsert — used by the draft hook.

Email

Layout lives in mail-templates/*.html (and optional .txt). After payload-site-forms init, these are copied to mail-templates/site-forms in your project and mailTemplatesPath is patched in automatically. To add or reset templates later, run payload-site-forms mail-templates.

Message copy is pulled from the CMS Mailing tab — if a body field is empty, the plugin falls back to a sensible default. Available merge tags include {{formTitle}}, {{submissionId}}, {{pipelineRunId}}, submitter {{email}}, and any submission field name.

To bypass the file + CMS pipeline entirely, pass renderEmailTemplate to the plugin: an async function that returns { subject, html?, text? }.

Plugin options

| Option | Description | | --- | --- | | fieldBlocks | Custom Payload blocks for form fields (defaults provided if omitted). | | additionalFormTypeOptions | Extra { label, value } entries for the legacy form-type select. Hidden by default; retained so custom form types still round-trip. | | overrides.siteForms | Deep partial override for the site-forms collection. | | overrides.siteFormSubmissions | Partial override for site-form-submissions. | | overrides.siteFormDraftSubmissions | Partial override for site-form-draft-submissions. | | overrides.siteFormPipelineRuns | Deprecated alias for the draft-submissions override. | | uploadCollectionSlug | Upload relation collection slug (default media). | | mailTemplatesPath | Directory containing {key}.html / {key}.txt; defaults to the package mail-templates/. | | lexicalEditorForMailing | Lexical editor instance for mailing-related rich fields. | | renderEmailTemplate | Async override: return { subject, html?, text? } and skip the file + CMS body pipeline. |

Public API (summary)

Server:

import {
  siteFormsPlugin,
  createDefaultFieldBlocks,
  lexicalJsonToPlainText,
  applyMergeTags,
} from '@agentmarketing/payload-site-forms'

Client-safe subpaths:

import {
  resolveInlineSteps,
  type InlineSequenceStep,
} from '@agentmarketing/payload-site-forms/resolve-sequence-steps'

import {
  normalizeSiteForm,
  type NormalizedSiteForm,
  type NormalizedStep,
  type SiteFormLike,
} from '@agentmarketing/payload-site-forms/normalize-site-form'

Requirements

  • Payload ^3.0.0
  • @payloadcms/richtext-lexical ^3.0.0 (peer)
  • @payloadcms/ui ^3.0.0 (peer — used by the admin UI helper component that gates the single-form vs. sequence editor UX)
  • react ^18 || ^19 (peer)
  • Next.js App Router

License

MIT.

Contact

Agent Marketing · [email protected]