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, 
});

TanStack Start route loaders run on both the server and the browser, so your docs route can load the document without loadDocument. Call a server function only when the browser doesn't have the document yet:

src/routes/docs/$.tsx
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, put documents in preloaded.docs instead.
  • 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:

src/routes/api/proxy.ts
import { openapi } from '@/lib/openapi';
import { createFileRoute } from '@tanstack/react-router';

const proxy = openapi.createProxy();

export const Route = createFileRoute('/api/proxy')({
  server: {
    handlers: {
      // Handles all methods by default, or specify individual methods (GET, POST, etc.)
      ANY: async ({ request }) => {
        return proxy.handle(request);
      },
    },
  },
});

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.

src/routes/api/oauth.ts
import { openapi } from '@/lib/openapi';
import { createFileRoute } from '@tanstack/react-router';

const handler = openapi.createOAuthHandler();

export const Route = createFileRoute('/api/oauth')({
  server: {
    handlers: {
      GET: async ({ request }) => handler(request),
    },
  },
});

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