createOpenAPI()
The OpenAPI server instance.
OpenAPI Server
The main config for Fumadocs OpenAPI.
It should not be referenced in browser environments.
input
The OpenAPI schemas to read from.
- File Paths
- External URLs
- Functions (see below)
Only schemas listed here can be loaded, other IDs passed to getSchema() or preloaded by pages throw an error.
Do not pass untrusted schemas to input, their $refs can read local files and fetch URLs, private hosts included.
import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
input: ['./unkey.json'],
});Loading Documents Once
Pages receive the whole document in their props, every page load, navigation and prefetch sends it again. For large documents, leave it out:
import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
// IDs end up in URLs, keep them URL-safe
input: { museum: './museum.yaml' },
includeDocument: false,
});On Next.js, Waku, React Router and Astro, <OpenAPIPage /> loads each document once per browser session with loadDocument, it also runs during server rendering:
'use client';
import { preload } from 'react-dom';
import { createOpenAPIPage } from 'fumadocs-openapi/ui';
export const OpenAPIPage = createOpenAPIPage({
async loadDocument(id) {
// `import.meta.env.SSR` on Waku, React Router and Astro
if (typeof window === 'undefined') {
// the browser starts downloading it with the page
preload(`/api/openapi/${id}`, { as: 'fetch', crossOrigin: 'anonymous' });
const { openapi } = await import('@/lib/openapi');
return (await openapi.getSchema(id)).bundled;
}
return (await fetch(`/api/openapi/${id}`)).json();
},
});And serve documents to the browser:
import { openapi } from '@/lib/openapi';
export async function GET(_req: Request, { params }: RouteContext<'/api/openapi/[id]'>) {
return Response.json((await openapi.getSchema((await params).id)).bundled);
}
// for static exports
export async function generateStaticParams() {
return Object.keys(await openapi.getSchemas()).map((id) => ({ id }));
}On Tanstack Start, route loaders run on both sides, call a server function only when the browser doesn't have the document yet:
import { createServerFn } from '@tanstack/react-start';
import type { Document } from 'fumadocs-openapi';
import { openapi } from '@/lib/openapi';
// documents the browser has loaded
const documents = new Map<string, Document>();
const getDocument = createServerFn({ method: 'GET' })
.validator((id: string) => id)
.handler(async ({ data: id }) => (await openapi.getSchema(id)).bundled);
export const Route = createFileRoute('/docs/$')({
component: Page,
loader: async ({ params }) => {
const data = await serverLoader({ data: params._splat?.split('/') ?? [] });
if (data.type === 'openapi') {
const { document: id, payload } = data.props;
payload.bundled = documents.get(id) ?? (await getDocument({ data: id }));
}
return data;
},
});
function Page() {
const page = useFumadocsLoader(Route.useLoaderData());
// the document of the first page too
if (page.type === 'openapi' && typeof window !== 'undefined')
documents.set(page.props.document, page.props.payload.bundled!);
// ...
}- With generated MDX files, only Tanstack Start needs a change: put documents in
preloaded.docsinstead. - For the UI installed by Fumadocs CLI, pass
loadDocumenttocreateOpenAPIRenderer(). - Browsers keep each document until a reload. With
disableCache, leaveincludeDocumenton.
Creating Proxy
A proxy server is useful for executing HTTP (fetch) requests, as it doesn't have CORS constraints like on the browser.
We use it for:
- Executing HTTP requests on the OpenAPI playground, when the target API endpoints do not have CORS configured correctly.
- OAuth requests of confidential clients (with a client secret) and device flows.
The proxy is public without authentication, it forwards the headers and body of requests, including Authorization. Restrict it with allowedOrigins and filterRequest.
Setup
Create a route handler for proxy server.
import { openapi } from '@/lib/openapi';
export const { GET, HEAD, PUT, POST, PATCH, DELETE } = openapi.createProxy();And set the proxy URL in createOpenAPI.
import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
proxyUrl: '/api/proxy',
});Allowed Origins
By default, the proxy accepts any public origin, refuses hosts on private networks (e.g. localhost), and doesn't forward your site's cookies.
List the origins to restrict it, or to proxy private APIs:
export const { GET, HEAD, PUT, POST, PATCH, DELETE } = openapi.createProxy({
// strings or regular expressions
allowedOrigins: ['https://api.example.com', 'http://localhost:8080'],
});Redirects are checked by the same rules.
Refusing private networks requires Node.js or Deno, specify allowedOrigins on other runtimes
(e.g. Bun).
Use filterRequest to refuse requests, for example to only allow reads:
export const { GET, HEAD, PUT, POST, PATCH, DELETE } = openapi.createProxy({
// proxied requests that return false are refused
filterRequest: (request) => request.method === 'GET',
});OAuth Redirect
OAuth providers only accept registered redirect URIs, but the API playground redirects to the current page by default. Create a route handler as the redirect URI of every page, it sends users back to the page that started the flow.
import { openapi } from '@/lib/openapi';
export const GET = openapi.createOAuthHandler();Pass its URL to createOpenAPIPage(), and register the full URL (e.g. https://docs.example.com/api/oauth) on your OAuth provider.
export const OpenAPIPage = createOpenAPIPage({
oauthRedirectUrl: '/api/oauth',
});Content Security Policy
The handler sends users back with a script served from its own route. If a Content Security Policy covers the route, it must allow 'self' scripts. Policies with 'strict-dynamic' ignore 'self', leave the route out of them instead.
How is this guide?
Last updated on
