Headless
The state and logic of API pages, without UI.
Overview
Everything outside fumadocs-openapi/ui is headless.
| Path | Module |
|---|---|
fumadocs-openapi | the page: the document, its servers and your components |
fumadocs-openapi/operation | an operation: its details, example requests and code usages |
fumadocs-openapi/playground | an API playground: its form, auth and requests |
fumadocs-openapi/requests | encoding the data of an example into a real request |
fumadocs-openapi/requests/generators | turning that request into code usages |
@fumadocs/json-schema/react | the Schema UI: JSON schemas flattened for UI |
@fumadocs/json-schema/react/client | the state of a Schema UI |
Page
createOpenAPIRenderer() takes your components and returns <OpenAPIPage />, which accepts the props of generated pages.
'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>
);
},
},
});| Component | Renders |
|---|---|
Operation | each operation and webhook, see Operation |
SchemaUI | the JSON schemas of parameters, bodies and responses, see Schema UI |
Layout | optional, wraps the rendered operations and webhooks |
Hooks
Components under the page read its state.
| Hook | Returns |
|---|---|
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.
'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>;
}| Hook | Returns |
|---|---|
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.
<SchemaUIProvider /> holds the state on top of it: the schemas opened from properties, the selected members of unions, and links to a property.
'use client';
import { useMemo } from 'react';
import { generateSchemaUI } from '@fumadocs/json-schema/react';
import { SchemaUIProvider, useSchemaUI } from '@fumadocs/json-schema/react/client';
import type { OpenAPIComponents } from 'fumadocs-openapi';
export const SchemaUI: OpenAPIComponents['SchemaUI'] = ({ client, ...options }) => {
const generated = useMemo(() => generateSchemaUI(options), [options.root]);
return (
<SchemaUIProvider id={client.rootId} name={client.name} generated={generated}>
<div id={client.rootId}>
<Schema />
</div>
</SchemaUIProvider>
);
};
function Schema() {
const { refs, path, open, back } = useSchemaUI();
const { schema } = path[path.length - 1];
return (
<>
{path.length > 1 && <button onClick={() => back(path.length - 2)}>Back</button>}
{schema.type === 'object' && (
<ul>
{schema.props.map((prop) => (
<li key={prop.name}>
{prop.name}:{' '}
<button onClick={() => open(prop.name, prop.$type)}>
{refs[prop.$type].typeName}
</button>
</li>
))}
</ul>
)}
</>
);
}useSchemaUI() | Is |
|---|---|
path | the opened schemas, starting from the root |
open(name, $ref), back(index) | navigate path |
getLink(index, name) | the URL linking to a property of a path item, visiting it opens path again |
highlighted | the property of the last path item that the URL links to |
Each item of path has the schema to show, and its unions with the selected members. select() of a union shows another member.
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, links to a property are resolved against it.
Render the slots of client
Bodies and responses pass client.selector to select their media type, and client.actions to
copy their TypeScript definitions.
infoTags are the labelled values an API page shows next to a field, like Format or Default. type decides how to open it further.
Prop
Type
Playground
usePlayground() holds the state of an API playground under <OperationProvider />. Its form starts from the selected example request, and edits sync back to the example, so code usages follow them.
'use client';
import { StfProvider, useFieldValue } from '@fumari/stf';
import { useOperation } from 'fumadocs-openapi/operation';
import { usePlayground } from 'fumadocs-openapi/playground';
export function MyPlayground() {
const { parameters } = useOperation();
const { stf, send, isSending, response } = usePlayground();
return (
<StfProvider value={stf}>
{parameters.map(({ in: location, items }) =>
items.map((param) => (
<ParameterInput key={`${location}.${param.name}`} location={location} name={param.name} />
)),
)}
<button disabled={isSending} onClick={send}>
Send
</button>
{response?.result.type === 'response' && <pre>{response.result.text()}</pre>}
</StfProvider>
);
}
function ParameterInput({ location, name }: { location: string; name: string }) {
const [value, setValue] = useFieldValue([location, name]);
return (
<input
aria-label={name}
value={String(value ?? '')}
onChange={(e) => setValue(e.target.value)}
/>
);
}| Field | Is |
|---|---|
stf | the form (a @fumari/stf data engine), holding path, query, header, cookie and body |
body | the request body edited in the form, with its preferred media type |
auth | the auth fields of the selected security requirement, select() switches it, authorize() obtains an OAuth 2.0 token into a field |
send(), isSending, response, clearResponse() | send the form as a request, and the result of the last one |
It accepts fetchOptions to customise requests, like onRequestInit(), authProviders to handle security schemes, see Authorization, and transformAuthInputs to customise the auth fields.
Requests
The parts usePlayground() builds on, to send a request without it.
| Export | Does |
|---|---|
createBrowserFetcher() | sends a request from the browser, through proxyUrl when set, its responses carry duration, mediaType, fileName and text() |
encodeRequestData() | turns the data of an example into a body, headers and query, from fumadocs-openapi/requests |
resolveMediaAdapter() | the adapter of a media type, undefined when unsupported |
usePlaygroundAuth() | the auth state of the page, its token store and the state of a flow |
useAuthFields() | the auth fields of an operation in a form, and the request data they encode into |
requestOAuthToken() | runs an OAuth 2.0 flow |
How is this guide?
Last updated on
