Skip to Content
Fumadocs

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, so every page load, navigation and prefetch sends it again. For large documents, leave it out:

lib/openapi.ts
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, 
});

Pass loadDocument to load each document once per browser session. It also runs during server rendering:

components/api-page.tsx
'use client';
import { preload } from 'react-dom';
import { createOpenAPIPage } from 'fumadocs-openapi/ui';

export const OpenAPIPage = createOpenAPIPage({
  async loadDocument(id) {
    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();
  },
});

Serve the documents to the browser from a route:

app/api/openapi/[id]/route.ts
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 }));
}
  • loadDocument works with generated MDX files too.
  • For the UI installed by Fumadocs CLI, pass loadDocument to createOpenAPIRenderer().
  • Browsers keep each document until a reload. With disableCache, leave includeDocument on.

Creating Proxy

A proxy server sends HTTP requests without the CORS restrictions of browsers. The API playground uses it for:

  • Requests to API endpoints that don't allow your origin with CORS.
  • 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 the proxy:

app/api/proxy/route.ts
import { openapi } from '@/lib/openapi';

export const { GET, HEAD, PUT, POST, PATCH, DELETE } = openapi.createProxy();

Then set its URL in createOpenAPI():

lib/openapi.ts
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 in your proxy route to restrict it, or to proxy private APIs:

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. On other runtimes, like Bun, specify allowedOrigins.

Use filterRequest to refuse requests, for example to only allow reads:

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.

app/api/oauth/route.ts
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:

components/api-page.tsx
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