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

payload-openapi-config

v0.0.1

Published

PayloadCMS v3 plugin that generates opt-in OpenAPI 3.1 specs

Readme

payload-openapi-config

CI Publish

A PayloadCMS v3 plugin that generates an OpenAPI 3.1 spec from your Payload config using a strict opt-in model. Collections and globals only appear in the spec if you explicitly wrap them — internal Payload system collections, admin-only routes, and anything else you haven't opted in stays completely invisible.

Should you use this package?

If you want a quick OpenAPI spec with minimal setup, check out payload-oapi first. It takes an simple opt-out approach and will require less setup.

This package is for situations where you need fine-grained control over what ends up in your public API docs:

  • You want only specific collections or globals in the spec
  • You need to expose read-only endpoints publicly while keeping write operations out
  • You want to filter by access function — only endpoints genuinely open to unauthenticated users
  • You need to enrich auto-generated operations with summaries, descriptions, custom response schemas, or tags
  • You have custom endpoints that need to appear alongside your standard CRUD routes

It will require more setup than a fully opt-out solution, but it gives you the tools to curate your API surface with precision.


Installation

pnpm add payload-openapi-config
# or
npm install payload-openapi-config
# or
yarn add payload-openapi-config

Peer dependencies — install these if you don't have them already:

pnpm add payload openapi-types

Quick start

1. Add the plugin

// payload.config.ts
import { buildConfig } from 'payload'
import payloadOpenAPIConfig from 'payload-openapi-config'

export default buildConfig({
  plugins: [
    payloadOpenAPIConfig({
      info: { title: 'My API', version: '1.0.0' },
    }),
  ],
  collections: [/* ... */],
})

This registers a GET /openapi.json endpoint. Nothing appears in the spec yet — you have to opt collections in explicitly.

2. Opt in a collection

// collections/Posts.ts
import { withSpec } from 'payload-openapi-config'
import type { CollectionConfig } from 'payload'

const Posts: CollectionConfig = withSpec({
  slug: 'posts',
  fields: [
    { name: 'title', type: 'text', required: true },
    { name: 'body', type: 'richText' },
  ],
})

export default Posts

With no second argument, all standard endpoints are included: list, findById, create, update, and delete.

3. Opt in a global

// globals/SiteSettings.ts
import { withGlobalSpec } from 'payload-openapi-config'

const SiteSettings = withGlobalSpec({
  slug: 'site-settings',
  fields: [{ name: 'siteName', type: 'text' }],
})

export default SiteSettings

Filtering endpoints

Pass a filter to the endpoints option to control which operations appear. All filter helpers are composable pure functions.

import { withSpec, readOnly, publicOnly, endpoints, excludingEndpoints, operations } from 'payload-openapi-config'

// Only GET endpoints (list + findById)
withSpec(Posts, { endpoints: readOnly() })

// Only endpoints whose Payload access function permits unauthenticated access
withSpec(Articles, { endpoints: publicOnly() })

// Explicit allowlist
withSpec(Orders, { endpoints: endpoints('list', 'findById', 'create') })

// Everything except delete
withSpec(Posts, { endpoints: excludingEndpoints('delete') })

// Filter by HTTP method
withSpec(Products, { endpoints: operations(['GET']) })
withSpec(Leads, { endpoints: operations(['GET', 'POST']) })

You can also write a custom filter function directly:

import type { EndpointFilter } from 'payload-openapi-config'

const myFilter: EndpointFilter = ({ collection }) => {
  const names = ['list', 'findById']
  if (collection.custom?.allowPublicCreate) names.push('create')
  return new Set(names)
}

withSpec(MyCollection, { endpoints: myFilter })

See docs/filters.md for the full filter reference including all endpoint names and async filter support.


Enriching generated operations

Auto-generated operations can be extended with summaries, descriptions, tags, and any other valid OpenAPI 3.1 OperationObject field. The fragment is deep-merged over the generated base — you only supply what you want to change.

withSpec(
  { slug: 'posts', fields: [] },
  {
    spec: {
      list: {
        description: 'Returns a paginated list of published posts.',
        tags: ['Content'],
      },
      create: {
        description: 'Creates a new post. Requires authentication.',
        responses: {
          '422': {
            description: 'Validation error',
            content: { 'application/json': { schema: { $ref: '#/components/schemas/ValidationError' } } },
          },
        },
      },
    },
  },
)

See docs/operation-overrides.md for custom endpoints, security overrides, and shared components.


Plugin options

payloadOpenAPIConfig({
  // Disable in specific environments without removing the plugin
  enabled: process.env.EXPOSE_SPEC === 'true',

  // Serve the spec at a custom path (default: '/openapi.json')
  specEndpoint: '/docs/openapi.json',

  // OpenAPI info object
  info: {
    title: 'Acme API',
    version: '2.1.0',
    description: 'Public-facing API for Acme Corp.',
    contact: { name: 'API Support', email: '[email protected]' },
  },

  // Deep-merged into the root spec document — use for servers, tags, extra components
  spec: {
    servers: [
      { url: 'https://api.acme.com', description: 'Production' },
    ],
    tags: [
      { name: 'Posts', description: 'Blog articles' },
    ],
  },
})

Caching

The spec is generated once per process and cached in memory. In NODE_ENV=development the cache is bypassed so every request reflects the latest config. To invalidate manually (e.g. in tests):

import { invalidateSpecCache } from 'payload-openapi-config'

invalidateSpecCache()

Further reading


License

MIT