OpenAPI
Generating docs for OpenAPI schema.
Setup
Install the required packages.
npm i fumadocs-openapi shikiGenerate Styles
Add the following line:
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
@import 'fumadocs-openapi/css/preset.css';Configure Plugin
Create the OpenAPI server instance & <OpenAPIPage /> component.
import { createOpenAPI } from 'fumadocs-openapi/server';
// note: this is a server-side API
export const openapi = createOpenAPI({
// the OpenAPI schema, you can also give it an external URL.
input: ['./openapi.json'],
});See createOpenAPI() & createOpenAPIPage() for available options.
Generate Pages
You can generate MDX files directly from your OpenAPI schema.
Create a script:
import { generateFiles } from 'fumadocs-openapi';
import { openapi } from '@/lib/openapi';
void generateFiles({
input: openapi,
output: './content/docs',
// we recommend to enable it
// make sure your endpoint description doesn't break MDX syntax.
includeDescription: true,
});Generate docs with the script:
bun ./scripts/generate-docs.tsAdd the OpenAPIPage component to your MDX components.
import { source } from '@/lib/source';
import { openapi } from '@/lib/openapi';
import { OpenAPIPage } from '@/components/api-page';
import { getMDXComponents } from '@/components/mdx';
// e.g. in your page renderer
export default function Page({ slug }) {
const page = source.getPage(slug);
const MdxContent = page.data.body;
return (
<MdxContent
components={getMDXComponents({
// add the MDX component
OpenAPIPage: async (props) => (
<OpenAPIPage {...await openapi.preloadOpenAPIPage(page)} {...props} />
),
})}
/>
);
}You can also use it without generating real files by integrating into Loader API.
import { loader } from 'fumadocs-core/source';
import { docs } from 'collections/server';
import { openapi } from '@/lib/openapi';
export const source = loader(
{
docs: docs.toFumadocsSource(),
openapi: await openapi.staticSource({
baseDir: 'openapi',
}),
},
{
baseUrl: '/docs',
plugins: [openapi.loaderPlugin()],
// ...
},
);staticSource() is a server-side API that generates pages directly to your loader(), hence it allows dynamic content generation, such as re-generating page tree as schema changes.
It will change the type of your pages, make sure to update all references to your source.
For example, where you return text for LLM:
import { source } from '@/lib/source';
export async function getLLMText(page: (typeof source)['$inferPage']) {
if (page.type === 'openapi') {
// e.g. return the stringified OpenAPI schema
return JSON.stringify(page.data.getSchema().bundled, null, 2);
}
// your original flow below...
}And update your page renderer:
import { OpenAPIPage } from '@/components/api-page';
export default function Page({ slug }) {
const page = source.getPage(slug);
// for OpenAPI pages
if (page.type === 'openapi') {
return (
<DocsPage full>
<h1 className="text-[1.75em] font-semibold">{page.data.title}</h1>
<DocsBody>
<OpenAPIPage {...page.data.getOpenAPIPageProps()} />
</DocsBody>
</DocsPage>
);
}
// your original flow below...
}Pass a client payload from server, then render the page using the <OpenAPIPage /> component you created above.
For example, in Tanstack Start:
import { createFileRoute, notFound } from '@tanstack/react-router';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { createServerFn } from '@tanstack/react-start';
import { source } from '@/lib/source';
import browserCollections from 'collections/browser';
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
import { useFumadocsLoader } from 'fumadocs-core/source/client';
import { type ReactNode, Suspense } from 'react';
import { OpenAPIPage } from '@/components/api-page';
export const Route = createFileRoute('/docs/$')({
component: Page,
loader: async ({ params }) => {
const slugs = params._splat?.split('/') ?? [];
const data = await serverLoader({ data: slugs });
// Fumadocs MDX: only preload content for normal pages
if (data.type === 'docs') {
await clientLoader.preload(data.path);
}
return data;
},
});
const serverLoader = createServerFn({
method: 'GET',
})
.inputValidator((slugs: string[]) => slugs)
.handler(async ({ data: slugs }) => {
const page = source.getPage(slugs);
if (!page) throw notFound();
const pageTree = await source.serializePageTree(source.getPageTree());
// different result for OpenAPI pages
if (page.type === 'openapi') {
return {
type: 'openapi',
title: page.data.title,
description: page.data.description,
pageTree,
props: page.data.getOpenAPIPageProps(),
};
}
return {
type: 'docs',
path: page.path,
pageTree,
// ...
};
});
const clientLoader = browserCollections.docs.createClientLoader({
component(pageData, props) {
// ...
},
});
function Page() {
const page = useFumadocsLoader(Route.useLoaderData());
let content: ReactNode;
// render OpenAPI page content
if (page.type === 'openapi') {
content = (
<DocsPage full>
<DocsTitle>{page.title}</DocsTitle>
<DocsDescription>{page.description}</DocsDescription>
<DocsBody>
{/* pass the payload data */}
<OpenAPIPage {...page.props} />
</DocsBody>
</DocsPage>
);
} else {
content = clientLoader.useContent(page.path, page);
}
return (
<DocsLayout tree={page.pageTree}>
<Suspense>{content}</Suspense>
</DocsLayout>
);
}You can see the full Tanstack Start example.
Ensure the migration is complete!
Run a type check to verify before continuing, e.g.
npm run types:checkFeatures
The official OpenAPI integration supports:
- Basic API endpoint information
- Interactive API playground
- Example code to send request (in different programming languages)
- Response samples and TypeScript definitions
- Request parameters and body generated from schemas
Demo
How is this guide?
Last updated on
