payload-openapi-config
v0.0.1
Published
PayloadCMS v3 plugin that generates opt-in OpenAPI 3.1 specs
Readme
payload-openapi-config
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-configPeer dependencies — install these if you don't have them already:
pnpm add payload openapi-typesQuick 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 PostsWith 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 SiteSettingsFiltering 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
- docs/basic-usage.md — full plugin setup, auth collections, upload collections, schema derivation
- docs/filters.md — all filter helpers with examples, endpoint name reference, custom filter functions
- docs/globals.md — opting in globals, restricting global endpoints
- docs/operation-overrides.md — enriching operations, custom endpoints, shared components
License
MIT
