OpenAPI
Generate OpenAPI 3.x MDX pages and use the partial AsyncAPI content-source and rendering APIs.
@publier/openapi generates MDX pages from OpenAPI 3.x specs and provides API rendering components. The API Reference section of this site was built with it. AsyncAPI support is a separate, partial content-source and component API, described below.
Install
pnpm add @publier/openapi yamlQuickstart
-
Place your spec at the project root:
openapi.yaml openapi: "3.1.0"info:title: My APIversion: "1.0.0"paths:/users:get:operationId: listUserssummary: List userstags: [Users]responses:"200":description: A list of users -
Write a generation script:
scripts/generate-api.ts import { generateFiles } from '@publier/openapi/generate';import { readFileSync } from 'node:fs';import { parse } from 'yaml';const spec = parse(readFileSync('./openapi.yaml', 'utf-8'));generateFiles({spec,outputDir: './src/content/docs/api',groupBy: 'tag', // group pages by tag (or 'path')per: 'operation', // one MDX file per operation}); -
Run the generator:
Terminal window bun run scripts/generate-api.tsThis writes one MDX file per operation and optional
meta.yamlinputs for custom routes. The default docs route discovers the generated MDX pages, but does not read those metadata files. -
Add an npm script for convenience:
package.json {"scripts": {"gen:api": "bun run scripts/generate-api.ts"}} -
Label and position the API group. Configure the default docs sidebar in
publier.config.yaml:publier.config.yaml docs:sidebar:meta:api: { label: API Reference, order: 30 }api/users: { label: Users, order: 1 }api/orders: { label: Orders, order: 2 }api/webhooks: { label: Webhooks, order: 3 }Match the keys to the directories emitted from your spec. A custom docs route can instead read the generated
meta.yamlfiles withreadMetaYaml()and pass the result tobuildSidebar(); creating those files alone does not configure the default sidebar. See Configuration. -
Run
publier dev— generated pages render with interactive components for parameters, request bodies, and code samples.
Code samples
Request examples are generated automatically in six languages: cURL, JavaScript (fetch), Python (requests), Go (net/http), Java (HttpClient), and C#.
To provide your own, add x-codeSamples to the operation in your spec — the generator uses those instead.
Server-side API
For programmatic access at build time:
import { createOpenAPI } from '@publier/openapi/server';
const openapi = createOpenAPI({ input: ['./openapi.yaml'] });
const op = await openapi.getOperation('listUsers');const get = await openapi.getOperationByPath('/users', 'get');const tag = await openapi.getOperationsByTag('Users');const nav = await openapi.getNavigation({ slugPrefix: 'api' });Multi-spec merging just works:
const merged = createOpenAPI({ input: ['./public-api.yaml', './admin-api.yaml'],});AsyncAPI
generateFiles() handles OpenAPI paths and webhooks; it does not generate MDX from AsyncAPI channels. Neither integrations.asyncapi nor integrations.openapi runs a generator today.
The package exports asyncapiSource() for channel page metadata and a navigation tree:
import { asyncapiSource } from '@publier/openapi';import { readFileSync } from 'node:fs';import { parse } from 'yaml';
const spec = parse(readFileSync('./asyncapi.yaml', 'utf-8'));const source = asyncapiSource({ spec, baseUrl: '/async-reference' });const pages = await source.getPages();const tree = await source.getPageTree();This does not write files, register Astro routes, or render page bodies. You must supply your own route and layout. The source correlates operations from the AsyncAPI 3-style top-level operations map with channel references; it does not translate AsyncAPI 2 publish/subscribe operations into that model.
For rendering in your own pages, the existing component exports are:
---import AsyncChannel from '@publier/openapi/components/async-channel';import AsyncMessage from '@publier/openapi/components/async-message';---AsyncChannel takes channelName, channel, and an operations array of { name, operation, messages } objects, with optional servers. AsyncMessage takes a message. These are rendering building blocks, not an automatic AsyncAPI documentation pipeline.
Components
| Component | Purpose |
|---|---|
APIReference | Single endpoint — params, body, responses |
APIPage | Full page layout for an operation |
APIPlayground | Interactive “try it” panel |
SchemaView | JSON Schema renderer |
CodeSamples | Multi-language request examples |
AsyncChannel | AsyncAPI channel view |
AsyncMessage | AsyncAPI message schema |
Import from subpath exports:
import APIReference from '@publier/openapi/components/api-reference';Interactive requests
APIPlayground is a separate component; generated APIReference pages do not automatically include it. Pass the parsed specification so the playground can resolve root security requirements and operation overrides:
---import APIPlayground from '@publier/openapi/components/api-playground';import { parse } from 'yaml';import { readFileSync } from 'node:fs';
const spec = parse(readFileSync('./openapi.yaml', 'utf8'));const operation = spec.paths['/users'].get;---
<APIPlayground method="get" path="/users" operation={operation} spec={spec} />The playground inherits root security unless the operation overrides it; security: [] removes authentication. A selector chooses between alternatives, while schemes in one requirement are applied together. API keys use their declared header or query name. HTTP bearer, HTTP basic, and manually obtained OAuth/OpenID access tokens are supported. The playground does not run an OAuth login flow.
Cookie authentication can opt in to sending existing browser cookies. You cannot type an arbitrary Cookie header; browser cookie, SameSite, and CORS policies still apply. Use a server that permits requests from your docs origin, and include that API origin in your host’s Content Security Policy connect-src directive. Unsupported or unresolved schemes produce a visible error.
Request bodies use the selected media type from requestBody.content. JSON and text are sent as raw text. For application/x-www-form-urlencoded and multipart/form-data, enter a JSON object of scalar fields. Fetch supplies the multipart boundary. File uploads and nested form encoding are not supported. GET and HEAD requests omit the body and do not add a JSON Content-Type.
Existing standalone playgrounds without a spec retain manual bearer and X-API-Key fields. Pass spec to use your API’s declared authentication.