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

@shefing/changes-button

v0.1.3

Published

A Payload CMS plugin that adds a **Changes** button to drafts-enabled documents. Clicking it opens a slide-in drawer that shows a field-by-field diff between the current document state (or the latest draft) and the currently published version — using the

Downloads

318

Readme

@shefing/changes-button

A Payload CMS plugin that adds a Changes button to drafts-enabled documents. Clicking it opens a slide-in drawer that shows a field-by-field diff between the current document state (or the latest draft) and the currently published version — using the same diff UI as the built-in Versions view.

Features

  • Auto-injects into every drafts-enabled collection / global (no manual component wiring).
  • Visibility is fully driven by document state: the button only shows when there are unpublished changes AND the user has publish permission.
  • Toggle inside the drawer to switch between Unsaved (live form values) and Latest draft (saved draft) when both exist.
  • Self-contained — the diff renderer is vendored from Payload's Versions view, so the plugin works against any payload / @payloadcms/next release without waiting for new exports to land upstream.
  • All UI strings live in src/labels.ts (en/ar/es/fr/he/zh) and are picked up via useTranslation().i18n.language.

Status

Two upstream Payload PRs would let the plugin install with a single line and zero layout edits, but they have not been merged yet:

| Feature | Issue | PR | | --- | --- | --- | | @payloadcms/next/views/diff subpath export | #16496 | #16498 | | config.admin.serverFunctions registry | #16497 | #16499 |

Until they ship, this package vendors the diff pipeline from @payloadcms/next/src/views/Version/RenderFieldsToDiff/ (see src/vendor/diff/) and requires a small (payload)/layout.tsx change to register the server function. Once both PRs land, the vendor copy can be deleted and the layout edit removed.

Install

pnpm add @shefing/changes-button

Usage

1. Register the plugin in payload.config.ts

import { buildConfig } from 'payload'
import { changesButtonPlugin } from '@shefing/changes-button'

export default buildConfig({
  // ...
  plugins: [
    changesButtonPlugin({
      // optional — exclude collections / globals from receiving the button
      excludedCollections: ['users'],
      excludedGlobals: [],
    }),
  ],
})

2. Wrap handleServerFunctions in app/(payload)/layout.tsx

The plugin needs a server function (shefing/changes-button:render-diff) registered alongside Payload's built-in ones. Until upstream PR #16499 lands, this is done by wrapping the serverFunction you pass to <RootLayout />:

// app/(payload)/layout.tsx
import type { ServerFunctionClient } from 'payload'
import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'
import { wrapServerFunctions } from '@shefing/changes-button/server'
import config from '@payload-config'
import { importMap } from './admin/importMap.js'

const baseServerFunction: ServerFunctionClient = async function (args) {
  'use server'
  return handleServerFunctions({ ...args, config, importMap })
}

const serverFunction = wrapServerFunctions(baseServerFunction)

export default async function Layout({ children }: { children: React.ReactNode }) {
  return (
    <RootLayout config={config} importMap={importMap} serverFunction={serverFunction}>
      {children}
    </RootLayout>
  )
}

wrapServerFunctions intercepts only the shefing/changes-button:render-diff key and forwards every other call to the base handler unchanged.

Configuration

| Option | Type | Default | Description | | --- | --- | --- | --- | | excludedCollections | string[] | [] | Slugs of collections that should NOT receive the Changes button. | | excludedGlobals | string[] | [] | Slugs of globals that should NOT receive the Changes button. | | disabled | boolean | false | Disable the plugin entirely without removing it from plugins. |

When the button appears

The button is rendered only when all of the following are true for the open document:

  • The entity has drafts enabled (versions.drafts is configured).
  • The current user has publish permission.
  • The document is not in trash.
  • There are unpublished changes — either the form is modified or unpublishedVersionCount > 0.

For brand-new entities (no published baseline) the diff renders against an empty baseline so every populated field shows as an addition.

Localization

All user-facing strings are declared in src/labels.ts and consumed via the getLabel(key, locale) helper. The active locale is read from useTranslation().i18n.language so the button automatically follows the admin UI language.

Built-in locales: en, ar, es, fr, he, zh. Missing keys/locales fall back to English.

Manual server-function wiring (advanced)

If you don't want to use wrapServerFunctions, register the handler explicitly in your serverFunctions map:

import { renderChangesDiffHandler, SERVER_FUNCTION_KEY } from '@shefing/changes-button/server'

const serverFunction: ServerFunctionClient = async function (args) {
  'use server'
  return handleServerFunctions({
    ...args,
    config,
    importMap,
    serverFunctions: { [SERVER_FUNCTION_KEY]: renderChangesDiffHandler },
  })
}

Local development

The vendored diff pipeline lives in src/vendor/diff/ — a snapshot of @payloadcms/next/src/views/Version/RenderFieldsToDiff/ (minus *.spec.ts). When upstream PR #16498 ships in a release:

  1. Replace the vendor imports in src/server/renderChangesDiff.tsx with import { countChangedFields, RenderDiff } from '@payloadcms/next/views/diff'.
  2. Delete src/vendor/diff/ and the copied SelectedLocalesContext.tsx.
  3. Bump the @payloadcms/next peerDependency to the release that exposes the subpath.

When PR #16499 ships, additionally:

  1. Re-add config.admin.serverFunctions self-registration in ChangesButtonPlugin.ts (see git history).
  2. Drop the wrapServerFunctions step from this README — the plugin will be a single-line install again.

License

MIT — © shefing