Skip to main content
doc0blade47/fumadocsdocumentation home
Sign in

OpenAPI Interactive UI

Last updated Oct 12, 2026
Relevant source files

The following files were used as context for generating this wiki page:

Overview

OpenAPI Interactive UI provides a component-based documentation rendering layer built on top of parsed OpenAPI and Swagger specifications, parsing Markdown processors, server state providers, and customizable page layouts into interactive API documentation pages. Sources: packages/openapi/src/ui/base.tsx, packages/openapi/src/server/index.tsx, packages/openapi/src/ui/index.tsx

By leveraging API Docs Primitives and Code Sample Generators, the system parses endpoint operations, request bodies, response status codes, and security schemes into rich interactive cards, tabbed request and response viewers, and copy-enabled code blocks. Sources: packages/openapi/src/ui/operation/index.tsx, packages/openapi/src/ui/operation/request-tabs.tsx, packages/openapi/src/ui/operation/response-tabs.tsx

Page Creation and Runtime Context

Initialization and Rendering Pipeline

OpenAPI documentation pages initialize their rendering contexts, Markdown processors, and server state by invoking base factory functions that construct client components. The primary initialization sequence proceeds through the following call chain: createOpenAPIPageBase → Schema → SchemaUI.

  1. createOpenAPIPageBase establishes the base React component, sets up the remark and rehype Markdown processing pipeline with remarkGfm and remarkRehype, dereferences bundled documents using dereferenceBundledDocument, and configures the RenderContext. Sources: packages/openapi/src/ui/base.tsx
  2. Schema receives the resolved schema and user options, invokes generateSchemaUI to compute schema hierarchies and reference tables, and passes the generated data to the client component. Sources: packages/api-docs/src/components/schema/index.tsx
  3. SchemaUI establishes anchor identifiers using useAnchorId, tracks object inspection navigation paths via component state, and hydrates interactive popovers or object property trees. Sources: packages/api-docs/src/components/schema/client.tsx

Sources: packages/openapi/src/ui/base.tsx:38-98, packages/api-docs/src/components/schema/index.tsx:89-112, packages/api-docs/src/components/schema/client.tsx:83-184

Note

createOpenAPIPageBase accepts an optional shiki factory for syntax highlighting alongside custom options for generating TypeScript definitions via compile from @fumari/json-schema-ts. Sources: packages/openapi/src/ui/base.tsx

Server State and Context Providers

Server state management is coordinated through ServerProvider and RenderContextProvider, which manage active API servers, URL variables, and local storage persistence. The context infrastructure relies on specific React contexts to supply runtime options across operation cards and schema renderers.

Context ProviderHookPurpose
RenderContextProvideruseRenderContext()Supplies the global RenderContext containing schema references, proxy URLs, Shiki options, and code usage generators.
ServerProvideruseServerContext()Manages active server selection, server variables, and persists selected configurations in local storage.

Sources: packages/openapi/src/ui/contexts/api.tsx:20-45, packages/openapi/src/ui/base.tsx:32-32

Warning

Both useRenderContext and useServerContext throw an error if called outside of their respective provider hierarchy, requiring components to be nested under <ApiProvider /> or the corresponding provider wrapper. Sources: packages/openapi/src/ui/contexts/api.tsx

Tip

ServerProvider initializes the active server configuration using storageKeyPrefix (defaulting to fumadocs-openapi- when using useStorageKey) to prevent state conflicts when multiple OpenAPI instances are mounted on the same origin. Sources: packages/openapi/src/ui/contexts/api.tsx, packages/openapi/src/ui/index.tsx

Interactive Operation Cards

Structural Decomposition and Operation Rendering

The Operation component serves as the core layout engine for individual HTTP endpoint cards and webhooks. It accepts properties defining the operation structure, including path, method, operation, pathItem, type, and headingLevel. When showTitle is enabled, the component computes the endpoint title using operation.summary, pathItem.summary, a humanized version of operation.operationId via idToTitle, or falls back to the raw path. If operation.deprecated is true alongside a title, a yellow status badge is rendered; when titles are hidden, deprecation warnings render as an inline callout.

export function Operation({
  type = 'operation',
  path,
  operation,
  pathItem,
  method,
  showTitle,
  showDescription,
  headingLevel = 2,
}: {
  type?: 'webhook' | 'operation';
  path: string;
  method: HttpMethods;
  operation: NoReference<OperationObject>;
  pathItem: NoReference<PathItemObject>;
 
  showTitle?: boolean;
  showDescription?: boolean;
  headingLevel?: number;
}) {
  const t = useTranslations({ note: 'operation page' });
  const ctx = useRenderContext();
  const {
    schema: { dereferenced },
  } = ctx;
  const body = operation.requestBody;
  let headNode: ReactNode = null;
  const operationDescription = operation.description ?? pathItem.description;
  const descriptionNode = showDescription && operationDescription && (
    <Markdown md={operationDescription} />
  );
  let bodyNode: ReactNode = null;
  let authNode: ReactNode = null;
  let responseNode: ReactNode = null;
  let callbacksNode: ReactNode = null;
  const exampleRequests = useMemo(
    () => getExampleRequests({ path, operation, method, pathItem, ctx }),
    [ctx, operation, method, pathItem, path],
  );
 
  if (showTitle) {
    const title =
      operation.summary ||
      pathItem.summary ||
      (operation.operationId ? idToTitle(operation.operationId) : path);
 
    headNode = (
      <div className="flex gap-2 items-center justify-between">
        <Heading id={title} depth={headingLevel} className="my-0!">
          {title}
        </Heading>
        {operation.deprecated && (
          <Badge color="yellow" className="text-xs not-prose">
            {t('Deprecated')}
          </Badge>
        )}
      </div>
    );
    headingLevel++;
  } else if (operation.deprecated) {
    headNode = <Callout type="warn" title={t('Deprecated')} className="mt-0!" />;
  }
 

Sources: packages/openapi/src/ui/operation/index.tsx:42-104

Warning

Media types defined within operation.requestBody are validated against registered media adapters via isMediaTypeSupported(type, ctx.mediaAdapters). If an unsupported media type is encountered, an immediate error is thrown: Media type ${type} is not supported (in ${path}). Sources: packages/openapi/src/ui/operation/index.tsx

Authentication Schemes and Security Contexts

Operations evaluate security requirements by checking operation.security with a fallback to dereferenced.security. Empty security object entries are filtered out. When securities are present on standard operations (type === 'operation'), they populate a tabbed selection interface driven by SelectTabs, mapping each security definition entry to its configured scopes and credential schemes retrieved from dereferenced.components?.securitySchemes.

const securities = (operation.security ?? dereferenced.security ?? []).filter(
  (v) => Object.keys(v).length > 0,
);
 
if (type === 'operation' && securities.length > 0) {
  const securitySchemes = dereferenced.components?.securitySchemes;
  const items = securities.map((security, i) => {
    return {
      value: String(i),
      label: (
        <div className="flex flex-col text-xs min-w-0">
          {Object.entries(security).map(([key, scopes]) => (
            <code key={key} className="truncate">
              <span className="font-medium">{key}</span>{' '}
              {scopes.length > 0 && (
                <span className="text-fd-muted-foreground">{scopes.join(', ')}</span>
              )}
            </code>
          ))}
        </div>
      ),
    };
  });
 
  authNode = (
    <SelectTabs defaultValue={items[0].value}>
      <div className="flex items-start justify-between gap-2 mt-10">
        <Heading id="authorization" depth={headingLevel} className="my-0!">
          {t('Authorization')}
        </Heading>
        {items.length > 1 ? (
          <SelectTabTrigger items={items} />
        ) : (
          <div className="not-prose">{items[0].label}</div>
        )}
      </div>
      {securities.map((security, i) => (
        <SelectTab key={i} value={items[i].value}>
          {Object.entries(security).map(([key, scopes]) => {
            const scheme = securitySchemes?.[key];
            if (!scheme) return;
 
            return <AuthScheme key={key} scheme={scheme} scopes={scopes} />;
          })}
        </SelectTab>
      ))}
    </SelectTabs>
  );

Sources: packages/openapi/src/ui/operation/index.tsx:208-255

Note

The Markdown component delegates parsing to processMarkdown(md) from useRenderContext() wrapped in a useMemo hook, supporting custom Markdown component overrides or direct return values from renderMarkdown. Sources: packages/openapi/src/ui/components/markdown.tsx

Operation Context and State Management

The OperationProvider component wraps interactive operation components, maintaining local state for selected examples and dispatching updates through a listener registry (ExampleUpdateListener[]). When setExample or setExampleData is invoked, registered listeners receive raw request payload data and encoded request representations to synchronize preview tabs and code generators.

export function OperationProvider({
  route,
  examples,
  defaultExampleId,
  children,
}: {
  route: string;
  examples: ExampleRequestItem[];
  defaultExampleId?: string;
  children: ReactNode;
}) {
  const [example, setExample] = useState(() => defaultExampleId ?? examples.at(0)?.id);
  const listeners = useRef<ExampleUpdateListener[]>([]);
 
  return (
    <OperationContext
      value={useMemo(
        () => ({
          example,
          route,
          setExample(newKey: string) {
            const example = examples.find((example) => example.id === newKey);
            if (!example) return;
 
            setExample(newKey);
            for (const listener of listeners.current) {
              listener(example.data, example.encoded);
            }
          },
          examples,
          setExampleData(data, encoded) {
            for (const item of examples) {
              if (item.id === example) {
                // persistent changes
                item.data = data;
                item.encoded = encoded;
                break;
              }
            }
 
            for (const listener of listeners.current) {
              listener(data, encoded);
            }
          },
          removeListener(listener) {
            listeners.current = listeners.current.filter((item) => item !== listener);
          },
          addListener(listener) {
            // initial call to listeners to ensure their data is the latest
            // this is necessary to avoid race conditions between `useEffect()`
            const active = examples.find((item) => item.id === example)!;
 
            listener(active.data, active.encoded);
            listeners.current.push(listener);
          },
        }),
        [example, route, examples],
      )}
    >
      {children}
    </OperationContext>
  );
}

Sources: packages/openapi/src/ui/operation/context.tsx:20-82

Tip

OperationProvider calls all registered listeners immediately upon registration in addListener using the currently active example configuration, preventing race conditions with asynchronous useEffect hooks. Sources: packages/openapi/src/ui/operation/context.tsx

Interactive Schema Viewer Popovers

Call-Chain Execution Walkthrough

Schema inspection relies on an anchored popover architecture defined across shared packages. The trace order begins at page initialization and terminates at the popover container:

  1. createOpenAPIPageBase builds the render context and passes the resolved document schema. Sources: packages/openapi/src/ui/base.tsx
  2. Schema receives root, resolver, and renderMarkdown options, memoizing generated schema structures. Sources: packages/api-docs/src/components/schema/index.tsx
  3. SchemaUI establishes anchor identifiers via useAnchorId and manages the current path inspection stack. Sources: packages/api-docs/src/components/schema/client.tsx
  4. PopoverContent renders the floating inspection container using accessible primitives from Radix UI Primitives. Sources: packages/radix-ui/src/components/ui/popover.tsx

Sources: packages/openapi/src/ui/base.tsx:38-127, packages/api-docs/src/components/schema/index.tsx:89-112, packages/api-docs/src/components/schema/client.tsx:83-185, packages/radix-ui/src/components/ui/popover.tsx:10-30

Note

ExcludedFromAutoAnchor tracks parsed root identifiers in a global Set<string> to prevent infinite loops when matching hash parameters in URL search strings against active component paths. Sources: packages/api-docs/src/components/schema/client.tsx

State Management and Popover Configuration

The interactive schema viewer utilizes shared state context (StateContextType) to track root identifiers, traversal paths, and generated schema references. The table below outlines the core properties managed by path items during interactive inspection.

PropertyTypeDescription
namestringThe property or field name being inspected.
$refstringReference identifier pointing to the schema definition.
scrollTopnumber (optional)Scroll position preserved across popover transitions.
highlightedstring (optional)Property name of the highlighted field for object structures.
tabValuesstring[] (optional)Active tab values for nested representations.
closedboolean (optional)Explicit popover closure state for the root item.

Sources: packages/api-docs/src/components/schema/client.tsx:34-56

Tip

When PopoverContent mounts or updates, popoverRef calculates a minimum height threshold using element.clientHeight + 2 and restores the prior scrollTop coordinate from the active path node. Sources: packages/api-docs/src/components/schema/client.tsx

Request Parameter Encoding

Execution Walkthrough and Sequence

Request parameters and query strings are prepared during the initial rendering cycle of each operation component OpenAPI Interactive UI. The serialization workflow follows a strict execution path starting from the component and passing through request generation, encoding wrappers, and specific parameter serializers:

  1. Operation initiates parameter collection from operation.parameters and pathItem.parameters, passing them along with context to getExampleRequests. Sources: packages/openapi/src/ui/operation/index.tsx
  2. getExampleRequests calls getRequestData to construct raw request objects via example picking or schema sampling, and then invokes encodeRequestData. Sources: packages/openapi/src/utils/get-example-requests.ts
  3. encodeRequestData loops over parameter locations (cookie, query, header, path), resolves media encoders, and dispatches parameters to type-specific formatters. Sources: packages/openapi/src/requests/media/encode.ts
  4. serializeQueryParameter inspects the parameter style and explosion rules, processing spaceDelimited, pipeDelimited, deepObject, arrays, and objects into final query representations. Sources: packages/openapi/src/requests/media/encode.ts

Sources: packages/openapi/src/ui/operation/index.tsx:77-80, packages/openapi/src/utils/get-example-requests.ts:45-74, packages/openapi/src/requests/media/encode.ts

Warning

When deepObject style is evaluated for query parameters, nested array behaviors are explicitly treated as undefined by wrapping values with Array.isArray(v) ? v : [String(v)] to prevent unexpected formatting edge cases. Sources: packages/openapi/src/requests/media/encode.ts

Parameter Serialization Strategies

The encoding module handles distinct parameter locations and styles according to OpenAPI specifications. The table below lists the serialization rules and style handlers implemented in the encoder.

Parameter Type / StyleExplode DefaultBehavior Description
path / labelfalsePrefixes the encoded string with a period (.) using comma or dotted separators.
path / matrixfalsePrefixes parameters with semicolon specifiers (;name=) for URI path matrix variables.
query / spaceDelimitedfalseJoins array elements using a single space character ( ) when explosion is disabled.
query / pipeDelimitedfalseJoins array elements using a pipe character (`
query / deepObjecttrueExpands nested object properties into bracketed query keys (param[key]=value).
header / simplefalseJoins array and object values using comma delimiters based on the explode flag.

Sources: packages/openapi/src/requests/media/encode.ts:83-252

Tip

If a parameter defines custom media adapters via its content field, getMediaEncoder resolves and executes the matching adapter rather than falling back to standard style serialization routines. Sources: packages/openapi/src/requests/media/encode.ts

Request and Response Tabs

Request Tab Rendering and Data Display

The RequestTabs component checks whether an operation defines a requestBody object before rendering. If a request body is present, it delegates to renderRequestTabs, falling back to renderRequestTabsDefault. When multiple example items exist, it wraps them in a Fumadocs UI Tabs component using each item's unique identifier as its value key. For single items, it renders the item directly, and when no examples are found, it outputs an empty state text.

Sources: packages/openapi/src/ui/operation/request-tabs.tsx:30-97

The RequestTabsItem component extracts requestData from each example item and populates a map of display names for parameter categories and bodies.

Request Data FieldDisplay Label / Source
bodyRequest Body (with badge showing requestData.bodyMediaType)
cookieCookie Parameters
headerHeader Parameters
queryQuery Parameters
pathPath Parameters

Sources: packages/openapi/src/ui/operation/request-tabs.tsx:106-121

Each available parameter or body category renders inside an accordion item containing a syntax-highlighted JSON code block.

Sources: packages/openapi/src/ui/operation/request-tabs.tsx:131-148

Note

If a request data category contains no keys or evaluates to falsy, RequestTabsItem skips rendering that accordion section entirely. Sources: packages/openapi/src/ui/operation/request-tabs.tsx

Response Tabs and Schema Sampling

The ResponseTabs component iterates over all response objects defined on an operation, extracting preferred media types and generating example payloads using schema sampling or pre-defined examples.

Sources: packages/openapi/src/ui/operation/response-tabs.tsx:53-103

PropertyTypeDescription
codestringHTTP response code (e.g., 200, 400).
responseNoReference<ResponseObject>The underlying OpenAPI response specification object.
mediaTypestring | nullPreferred media type resolved via getPreferredType.
examplesResponseExample[]Array of generated or defined response example items.

Sources: packages/openapi/src/ui/operation/response-tabs.tsx:20-33

Response tabs render as a group of tabs keyed by HTTP status code. When a response defines multiple examples, they are organized within a single-selection accordion component defaulting to the first index.

Sources: packages/openapi/src/ui/operation/response-tabs.tsx:115-159

Warning

If a response object specifies neither explicit examples nor a schema, response tab generation yields an empty array, causing ResponseTabs to return null. Sources: packages/openapi/src/ui/operation/response-tabs.tsx

Client Code Blocks and Usage Generation

The UsageTabs component initializes a code usage generator registry using createCodeUsageGeneratorRegistry, appending inline generators from context samples and x-codeSamples extensions on the operation object.

Sources: packages/openapi/src/ui/operation/usage-tabs.tsx:30-90

The ClientCodeBlock component abstracts syntax highlighting by checking render context overrides before falling back to DynamicCodeBlock with Shiki initialization.

Sources: packages/openapi/src/ui/components/codeblock.tsx:8-21

Alternative Scalar Client Integration

The withScalar Wrapper and Client Component

Fumadocs supports delegating endpoint interaction and testing to the @scalar/api-client-react library by wrapping page generation options inside withScalar. The function requires @scalar/api-client-react to be installed in the project, automatically importing its style sheet from @scalar/api-client-react/style.css and loading the client component lazily.

Sources: packages/openapi/src/scalar/client.tsx:10, packages/openapi/src/scalar/index.tsx:5-10

Option PropertyReturn / ActionDescription
playground.providerprops.childrenBypasses default playground provider wrappers to use Scalar context.
playground.render<Client method={method} path={path} spec={ctx.schema.bundled} />Renders the lazy-loaded ScalarPlayground client component for each endpoint.

Sources: packages/openapi/src/scalar/index.tsx:15-23

Note

The withScalar function overrides playground.provider to pass children directly and assigns playground.render to mount ScalarPlayground with the bundled OpenAPI specification schema. Sources: packages/openapi/src/scalar/index.tsx

Playground Rendering and Execution

The ScalarPlayground component renders an interactive card containing an HTTP method label, the request path, and a submit trigger button that opens the Scalar client modal with the specified path and method.

Sources: packages/openapi/src/scalar/client.tsx:12-53

const client = useApiClient({
  configuration: {
    content: spec as never,
  },
});

Sources: packages/openapi/src/scalar/client.tsx:24-28

The component utilizes useTheme from fumadocs-ui/provider/base to resolve the current application theme and synchronize light or dark mode styling for the Scalar client wrapper div once mounted on the client.

Sources: packages/openapi/src/scalar/client.tsx

Warning

ScalarPlayground is marked with 'use client' and relies on useState and useEffect hooks to track mounting status before applying resolved theme mode class names to avoid server-side hydration mismatches. Sources: packages/openapi/src/scalar/client.tsx

Was this page helpful?