Fumadocs

Headless

The state and logic of API pages, without UI.

Overview

Everything outside fumadocs-openapi/ui is headless.

PathModule
fumadocs-openapithe page: the document, its servers and your components
fumadocs-openapi/operationan operation: its details, example requests and code usages
fumadocs-openapi/playgroundthe auth state and the fetcher of the playground
fumadocs-openapi/requestsencoding the data of an example into a real request
fumadocs-openapi/requests/generatorsturning that request into code usages

Page

createOpenAPIRenderer() takes your components and returns <OpenAPIPage />, which accepts the props of generated pages.

components/api-page.tsx
'use client';
import { type CodeBlockProps, createOpenAPIRenderer } from 'fumadocs-openapi';
import { Operation } from '@/components/my-operation';
import { SchemaUI } from '@/components/my-schema';

export const OpenAPIPage = createOpenAPIRenderer({
  components: {
    Operation,
    SchemaUI,
    CodeBlock({ lang, code }: CodeBlockProps) {
      return (
        <pre>
          <code className={`language-${lang}`}>{code}</code>
        </pre>
      );
    },
  },
});
ComponentRenders
Operationeach operation and webhook, see Operation
SchemaUIthe JSON schemas of parameters, bodies and responses, see Schema UI
Layoutoptional, wraps the rendered operations and webhooks

Hooks

Components under the page read its state.

HookReturns
useOpenAPI()the dereferenced document (doc) and request options
useComponents()the components passed to the page
useServer()the selected server and its variables
useTypeScriptDefinitions(schema, options)TypeScript definitions of a JSON schema
useRenderContext()the render options of the page, like playground

Operation

<OperationProvider /> derives the details of an operation or webhook, and holds its selected example request.

components/my-operation.tsx
'use client';
import {
  OperationProvider,
  type PageOperationProps,
  useCodeUsage,
  useOperation,
  useResponseExamples,
} from 'fumadocs-openapi/operation';

export function Operation(props: PageOperationProps) {
  return (
    <OperationProvider {...props}>
      <Content />
    </OperationProvider>
  );
}

function Content() {
  const { title, parameters, codeUsages } = useOperation();
  const responses = useResponseExamples();

  return (
    <>
      <h2>{title}</h2>
      {parameters.map(({ in: location, items }) => (
        <section key={location}>{items.map((param) => param.name).join(', ')}</section>
      ))}
      {Array.from(codeUsages.map().keys(), (id) => (
        <CodeUsage key={id} id={id} />
      ))}
      {responses.map((tab) => (
        <pre key={tab.code}>{JSON.stringify(tab.examples?.[0]?.sample)}</pre>
      ))}
    </>
  );
}

function CodeUsage({ id }: { id: string }) {
  const code = useCodeUsage(id);
  return code && <pre>{code}</pre>;
}
HookReturns
useOperation()the operation with its details resolved
useExampleRequests()the example requests, the selected one, select() and update() to edit its data
useExampleRequest()data of the selected example, following its updates
useCodeUsage(id)the code generated by codeUsages for the selected example
useResponseExamples()responses with example values of their preferred media type

Prop

Type

Schema UI

generateSchemaUI() flattens a JSON schema: every $ref it reaches becomes an entry in refs, so your UI walks a map from $root instead of recursing.

components/my-schema.tsx
'use client';
import { useMemo } from 'react';
import { generateSchemaUI } from '@fumadocs/json-schema/react';
import type { OpenAPIComponents } from 'fumadocs-openapi';

export const SchemaUI: OpenAPIComponents['SchemaUI'] = ({ client, ...options }) => {
  const { $root, refs } = useMemo(() => generateSchemaUI(options), [options.root]);
  const schema = refs[$root];
  if (schema.type !== 'object') return null;

  return (
    <ul>
      {schema.props.map((prop) => (
        <li key={prop.name}>
          {prop.name}: {refs[prop.$type].typeName}
        </li>
      ))}
    </ul>
  );
};

The page passes renderMarkdown and renderCodeblock in, so descriptions and examples render through your own components. client carries the anchor ID and name of the root, for deep links into a property.

Playground

Send a request without the built-in playground.

import { createBrowserFetcher, usePlaygroundAuth } from 'fumadocs-openapi/playground';
import { encodeRequestData } from 'fumadocs-openapi/requests';
ExportDoes
usePlaygroundAuth()the auth state of the page, its token store and the state of a flow
createBrowserFetcher()sends a request from the browser, through proxyUrl when set
encodeRequestData()turns the data of an example into a body, headers and query
resolveMediaAdapter()the adapter of a media type, isMediaTypeSupported() checks it first

How is this guide?

Last updated on

On this page