Skip to main content
doc0blade47/fumadocsdocumentation home
Sign in

OpenAPI Document Generation

Last updated Oct 12, 2026100% grounded
Relevant source files

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

Overview

OpenAPI Document Generation automates the transformation of OpenAPI specifications into structured, type-safe MDX documentation pages for OpenAPI Interactive UI. By combining document loading, bundling, and schema upgrading through @scalar/openapi-upgrader and @fumadocs/api-docs/schema/bundle, the package processes input definitions into intermediate entry trees that organize paths, operations, tags, and webhooks.

Sources: packages/openapi/src/utils/document/load.ts:1-19, packages/openapi/src/utils/pages/builder.ts:1-61

It integrates with server loaders and file generation workflows to serialize static metadata, build table-of-contents trees, and output .mdx files alongside navigation manifests.

Sources: packages/openapi/src/server/index.tsx:149-206, packages/openapi/src/generate-file.ts:122-173

OpenAPI Specification Ingestion and Upgrading

Document Loading and Upgrading Pipeline

The public API surface for OpenAPI document loading and upgrading processes raw specification inputs through bundling and normalization pipelines. Exported types expose specification versions (OpenAPIV2, OpenAPIV3, OpenAPIV3_1, OpenAPIV3_2, and the union OpenAPI) alongside media adapters and builder configurations.

export * from './generate-file';
export type { MediaAdapter } from '@/requests/media/adapter';
export type { RenderContext } from './types';
export type {
  OperationOutput,
  OutputEntry,
  OutputGroup,
  PageOutput,
  PagesBuilder,
  PagesBuilderConfig,
  WebhookOutput,
  OperationItem,
  WebhookItem,
  GeneratedPageProps,
} from './utils/pages/builder';
export type { SchemaToPagesOptions } from './utils/pages/preset-auto';
export type { OpenAPIV3_2, OpenAPIV3_1, OpenAPIV3, OpenAPIV2, OpenAPI } from '@/types/openapi';

Sources: packages/openapi/src/index.ts:1-17

The asynchronous loadDocument function accepts either a file path string or a pre-parsed document object. It resolves and bundles external references via bundle from @fumadocs/api-docs/schema/bundle, then normalizes the resulting structure to target version 3.2 using upgrade from @scalar/openapi-upgrader. Any resolution or upgrade failure catches the error and throws a descriptive [OpenAPI] Failed to resolve input error containing the original cause.

export async function loadDocument(input: string | Document): Promise<{
  bundled: Document;
}> {
  try {
    let bundled = await bundle<Document>(input);
    bundled = upgrade(bundled, '3.2') as Document;
    return { bundled };
  } catch (e) {
    throw new Error(`[OpenAPI] Failed to resolve input: ${input}`, {
      cause: e,
    });
  }
}

Sources: packages/openapi/src/utils/document/load.ts:8-20

Warning

If an OpenAPI specification contains circular references or unresolvable external $ref pointers, bundle throws during loadDocument execution, which aborts the loading process before the upgrade step can run.

Sources: packages/openapi/src/utils/document/load.ts:11-19

Document Parsing and Hierarchy Construction

Intermediate Document Structures and Entry Types

The schema parsing engine decomposes bundled OpenAPI documents into structured intermediate outputs using the fromSchema function. It provides a PagesBuilder context that exposes helper methods like extract, dereferenceShallow, fromExtractedOperation, and routePathToFilePath to customize output generation.

interface BaseEntry {
  path: string;
  schemaId: string;
  info: {
    title: string;
    description?: string;
    deprecated?: boolean;
  };
}
 
export interface OperationOutput extends BaseEntry {
  type: 'operation';
  item: OperationItem;
}
 
export interface WebhookOutput extends BaseEntry {
  type: 'webhook';
  item: WebhookItem;
}
 
export interface PageOutput extends BaseEntry {
  type: 'page';
  operations: OperationItem[];
  webhooks: WebhookItem[];
  /** tag info if the page is generated from a tag. */
  tag?: TagObject;
}
 
export interface OutputGroup extends BaseEntry {
  type: 'group';
  entries: OutputEntry[];
  /** tag info if the group is generated from a tag. */
  tag?: TagObject;
}
 
export interface WebhookItem {
  /**
   * webhook name in `webhooks`
   */
  name: string;
  method: HttpMethods;
}
 
export interface OperationItem {
  /**
   * the path of operation in `paths`
   */
  path: string;
  /**
   * the HTTP method of operation
   */
  method: HttpMethods;
}
 
export type OutputEntry = PageOutput | OperationOutput | WebhookOutput | OutputGroup;

Sources: packages/openapi/src/utils/pages/builder.ts:7-61

The parser iterates over bundled.paths and bundled.webhooks across all supported methodKeys to populate extracted operations and webhooks. The routePathToFilePath utility transforms route paths by converting characters to lowercase, replacing dots with hyphens, stripping surrounding curly braces from parameter segments, and joining segments with slashes.

export function fromSchema(
  schemaId: string,
  bundled: Document,
  config: PagesBuilderConfig,
): OutputEntry[] {
  const files: OutputEntry[] = [];
  const { toPages } = config;
 
  toPages({
    id: schemaId,
    document: bundled,
    dereferenceShallow: (s) => dereferenceShallow(s, bundled),
    create(entry) {
      files.push(entry);
    },
    extract() {
      const result: ExtractedInfo = { webhooks: [], operations: [] };
 
      for (const [path, pathItem] of Object.entries(bundled.paths ?? {})) {
        if (!pathItem) continue;
 
        for (const methodKey of methodKeys) {
          if (!pathItem[methodKey]) continue;
 
          result.operations.push({
            method: methodKey,
            path,
            tags: pathItem[methodKey]?.tags,
          });
        }
      }
 
      for (const [name, _pathItem] of Object.entries(bundled.webhooks ?? {})) {
        const pathItem = dereferenceShallow(_pathItem, bundled);
        if (!pathItem) continue;
 
        for (const methodKey of methodKeys) {
          if (!pathItem[methodKey]) continue;
 
          result.webhooks.push({
            method: methodKey,
            name,
            tags: pathItem[methodKey]?.tags,
          });
        }
      }
 
      return result;
    },
    routePathToFilePath(path) {
      return path
        .toLowerCase()
        .replaceAll('.', '-')
        .split('/')
        .flatMap((v) => {
          if (v.startsWith('{') && v.endsWith('}')) return v.slice(1, -1);
          if (v.length === 0) return [];
          return v;
        })
        .join('/');
    },

Sources: packages/openapi/src/utils/pages/builder.ts:121-181

Output Entry TypeInterface NameDescription
operationOperationOutputRepresents a single API operation endpoint entry.
webhookWebhookOutputRepresents an event-driven webhook endpoint entry.
pagePageOutputContains multiple operations and webhooks, with optional tag metadata.
groupOutputGroupHolds a nested collection of child output entries and optional tag info.

Sources: packages/openapi/src/utils/pages/builder.ts:17-40

Note

When resolving display names for operations, fromExtractedOperation evaluates operation.summary, then pathItem.summary, and falls back to a title-cased operation.operationId or the raw path string if neither summary is present.

Sources: packages/openapi/src/utils/pages/builder.ts:195-211

Execution Walkthrough and Sequence Diagram

The hierarchy construction and virtual file translation pipeline processes output entries through a multi-stage call chain.

  1. createOpenAPI initializes the server state and triggers schema loading.
  2. getVirtualFiles calls fromSchema to generate the raw entry list and invokes onEntries.
  3. onEntries recursively iterates through entry collections or builds meta.json navigation lists when meta generation is enabled.
  4. onEntry receives individual non-group entries and invokes getPageProps for each.
  5. getPageProps constructs the final page property payloads for documentation rendering.
export function createOpenAPI(options: OpenAPIOptions = {}): OpenAPIServer {
  const { disableCache = false } = options;
  const schemaMap = new Map<string, Promise<LoadedDocument>>();
 
  let resolvedInput: SchemaRecord = {};
  if (Array.isArray(options.input)) {
    for (const item of options.input) resolvedInput[item] = item;
  } else if (options.input) {
    resolvedInput = options.input;
  }
 
  function getSchema(schemaId: string): Promise<LoadedDocument> {
    if (!(schemaId in resolvedInput)) {
      console.warn(
        `[Fumadocs OpenAPI] the document "${schemaId}" is not listed in the input array, this may be unexpected and won't be cached properly.`,
      );
      // do not cache unlisted documents
      return loadDocument(schemaId);
    }
 
    if (!disableCache) {
      const cached = schemaMap.get(schemaId);
      if (cached) return cached;
    }
 
    const raw = resolvedInput[schemaId];
    const output = Promise.resolve(typeof raw === 'function' ? raw() : raw).then(loadDocument);
    if (!disableCache) schemaMap.set(schemaId, output);
    return output;
  }
 
  async function getSchemas(): Promise<Record<string, LoadedDocument>> {
    const entries = await Promise.all(
      Object.keys(resolvedInput).map(async (k) => [k, await getSchema(k)]),
    );
    return Object.fromEntries(entries);
  }
 
  async function getVirtualFiles(server: OpenAPIServer, options: OpenAPISourceOptions) {
    const { baseDir = '', meta = false } = options;
    const { createAutoPreset } = await import('@/utils/pages/preset-auto');
    const { fromSchema } = await import('@/utils/pages/builder');
    const files: VirtualFile<{
      pageData: OpenAPIPageData;
      metaData: MetaData;
    }>[] = [];
 
    const schemas = await server.getSchemas();
    const builderOptions = createAutoPreset(options);
 
    for (const [id, schema] of Object.entries(schemas)) {
      const list = fromSchema(id, schema.bundled, builderOptions);
 
      onEntries(list);
 
      function onEntry(entry: PageOutput | OperationOutput | WebhookOutput) {
        const props = getPageProps(entry);
 
        files.push({
          type: 'page',
          path: `${baseDir}/${entry.path}`,
          data: {
            ...entry.info,
            getAPIPageProps() {
              return this.getOpenAPIPageProps();
            },
            getClientAPIPageProps() {
              return this.getOpenAPIPageProps();
            },
            getOpenAPIPageProps() {
              return {
                payload: {
                  bundled: schema.bundled,
                  proxyUrl: server.options.proxyUrl,
                },
                ...props,
              };
            },
            getSchema() {
              return {

Sources: packages/openapi/src/server/index.tsx:111-206

Sources: packages/openapi/src/server/index.tsx:111-206

Caution

If meta is configured with folder styles, onEntries computes relative paths using path.relative between parent and child entries; omitting valid parent relationships causes malformed meta.json page arrays.

Sources: packages/openapi/src/server/index.tsx:221-252

Static Data and Table Extraction

Slugged Metadata Generation

The toStaticData utility function builds table of contents items (TOCItemType[]) and structured metadata (StructuredData) for generated documentation pages. It accepts a GeneratedPageProps page configuration and an OpenAPI Document, iterating through both configured operations and webhooks.

export function toStaticData(
  page: GeneratedPageProps,
  doc: Document,
): {
  toc: TOCItemType[];
  structuredData: StructuredData;
} {

Sources: packages/openapi/src/utils/pages/to-static-data.ts:9-15

For each operation or webhook entry, toStaticData resolves the operation object by shallow dereferencing path or webhook maps against the root document. If page.showTitle is enabled and item.operationId exists, it calculates a heading title using item.summary, falls back to idToTitle(item.operationId), or defaults to the path string. The Slugger utility from github-slugger generates URL-safe anchors (#id) for the table of contents and heading arrays.

const slugger = new Slugger();
const toc: TOCItemType[] = [];
const structuredData: StructuredData = { headings: [], contents: [] };
 
function pathItem(item: OperationObject, defaultTitle: string) {
  if (page.showTitle && item.operationId) {
    const title = item.summary || (item.operationId ? idToTitle(item.operationId) : defaultTitle);
    const id = slugger.slug(title);
 
    toc.push({
      depth: 2,
      title,
      url: `#${id}`,
    });
    structuredData.headings.push({
      content: title,
      id,
    });
  }
 
  if (item.description)
    structuredData.contents.push({
      content: item.description,
      heading: structuredData.headings.at(-1)?.id,
    });
}

Sources: packages/openapi/src/utils/pages/to-static-data.ts:16-41

Note

If an operation defines a description, it is automatically appended to structuredData.contents linked to the immediately preceding heading identifier.

Sources: packages/openapi/src/utils/pages/to-static-data.ts:36-41

Operation and Webhook Iteration Pipeline

The function processes collection loops for both HTTP path operations and asynchronous webhooks, resolving references through dereferenceShallow.

for (const item of page.operations ?? []) {
  const operation = dereferenceShallow(doc.paths?.[item.path], doc)?.[item.method];
  if (!operation) continue;
 
  pathItem(operation, item.path);
}
 
for (const item of page.webhooks ?? []) {
  const webhook = dereferenceShallow(doc.webhooks?.[item.name], doc)?.[item.method];
  if (!webhook) continue;
 
  pathItem(webhook, item.name);
}
 
return { toc, structuredData };

Sources: packages/openapi/src/utils/pages/to-static-data.ts:43-57

Collection SourceLookup TargetDereference SourceFallback Title Key
page.operationsdoc.paths?.[item.path]docitem.path
page.webhooksdoc.webhooks?.[item.name]docitem.name

Sources: packages/openapi/src/utils/pages/to-static-data.ts:43-55

Warning

If dereferenceShallow returns a falsy operation or webhook object, the iteration skips the entry entirely without adding entries to the table of contents or structured metadata.

Sources: packages/openapi/src/utils/pages/to-static-data.ts:44-45, packages/openapi/src/utils/pages/to-static-data.ts:51-52

Virtual File Routing and Presets

Preset Algorithms and Path Generation

The automatic page generation preset configures how endpoint schemas translate into virtual routes. The createAutoPreset function inspects options.name to determine naming behavior, supporting either custom naming functions or configuration objects with an algorithm property set to either 'v1' or 'v2'.

  /**
   * The version of algorithm used to generate file paths.
   *
   * v1: Fumadocs OpenAPI v8
   * v2: Fumadocs OpenAPI v9
   *
   * @defaultValue v2
   */
  algorithm?: 'v2' | 'v1';
}

Sources: packages/openapi/src/utils/pages/preset-auto.ts:81-90

When result.type is an operation, algorithm 'v2' uses operation.operationId when available. If algorithm is 'v1' or operationId is omitted, it falls back to joining this.routePathToFilePath(result.item.path) with result.item.method.toLowerCase(). For webhooks, algorithm 'v2' similarly inspects hook.operationId via dereferenceShallow, falling back to slugify(result.item.name).

  if (result.type === 'operation') {
    const operation = this.document.paths![result.item.path]![result.item.method]!;
 
    if (algorithm === 'v2' && operation.operationId) {
      return operation.operationId;
    }
 
    return path.join(
      this.routePathToFilePath(result.item.path),
      result.item.method.toLowerCase(),
    );
  }
 
  const hook = dereferenceShallow(this.document.webhooks![result.item.name], this.document)[
    result.item.method
  ]!;
 
  if (algorithm === 'v2' && hook.operationId) {
    return hook.operationId;
  }
 
  return slugify(result.item.name);
};

Sources: packages/openapi/src/utils/pages/preset-auto.ts:125-147

Warning

Relying on algorithm 'v2' without explicit operationId definitions on operations or webhooks causes path generation to fall back directly to route path joining or slugified names.

Sources: packages/openapi/src/utils/pages/preset-auto.ts:128-135, packages/openapi/src/utils/pages/preset-auto.ts:142-146

Virtual File Resolution Workflow

The virtual file generation workflow converts loaded schema documents into structured file representations for server integrations. The execution walkthrough follows this exact sequence: createOpenAPI instantiates the server instance, which invokes getVirtualFiles to iterate over schemas, and passes output lists to onEntries.

Sources: packages/openapi/src/server/index.tsx:111-216

  1. createOpenAPI — Initializes schemaMap caches and returns the server object containing getVirtualFiles.
  2. getVirtualFiles — Retrieves bundled schemas via server.getSchemas(), builds builder options using createAutoPreset, and invokes fromSchema.
  3. onEntries — Recursively flattens or structures output entries, respecting meta options and folderStyle configurations.
  4. onEntry — Maps each leaf entry into a virtual file object carrying OpenAPIPageData props, metadata, and static data handlers.

Sources: packages/openapi/src/server/index.tsx:111-206

Note

If meta is enabled as an object with folderStyle: 'separator', group entries push title separators (---title---) alongside path references (...relativePath) into the emitted meta.json pages array.

Sources: packages/openapi/src/server/index.tsx:221-242

MDX Document Serialization

String Template Conversion and Component Layouts

The toText function converts page outputs, operations, and webhooks into serialized MDX documents by orchestrating frontmatter formatting, metadata insertion, and component layout templates. It accepts an entry, a doc document object, and options of type PagesToTextOptions.

export function toText(
  entry: PageOutput | OperationOutput | WebhookOutput,
  doc: Document,
  options: PagesToTextOptions = {},
) {
  const { frontmatter, includeDescription = false } = options;
  const extend = frontmatter?.(
    entry.info.title,
    entry.info.description,
    entry.type === 'page'
      ? entry.tag
        ? { type: 'tag', tag: entry.tag }
        : { type: 'file' }
      : { type: 'operation' },
  );
  const pageProps = getPageProps(entry);
  if (!includeDescription) {
    pageProps.showDescription = false;
  }
 
  let meta: InternalOpenAPIMeta = {
    preload: [entry.schemaId],
  };
  if (entry.type === 'operation' || entry.type === 'webhook') {
    const operation = entry.item;
 
    meta.method = operation.method.toUpperCase();
    meta.webhook = entry.type === 'webhook';
    meta.deprecated = entry.info.deprecated;
  }
 
  const data = toStaticData(pageProps, doc);
 
  return generateDocument(
    {
      title: entry.info.title,
      description: !includeDescription ? entry.info.description : undefined,
      full: true,
      ...extend,
      _openapi: {
        ...meta,
        ...data,
        ...(extend?._openapi as object | undefined),
      },
    },
    pageContent(pageProps),
    options,
  );
}

Sources: packages/openapi/src/utils/pages/to-text.ts:57-105

The pageContent function generates a React layout component string that exposes APIPage and OpenAPIPage components for backward compatibility, forwarding document, webhooks, and operations props.

function pageContent({
  showTitle,
  showDescription,
  document,
  webhooks,
  operations,
}: GeneratedPageProps): string {
  const propStrs: string[] = [`document=${doubleQuote(document)}`];
 
  // filter extra properties in props
  if (webhooks) {
    propStrs.push(
      `webhooks={${JSON.stringify(
        webhooks.map(
          (item) =>
            ({
              name: item.name,
              method: item.method,
            }) satisfies WebhookItem,
        ),
      )}}`,
    );
  }
  if (operations) {
    propStrs.push(
      `operations={${JSON.stringify(
        operations.map(
          (item) =>
            ({
              path: item.path,
              method: item.method,
            }) satisfies OperationItem,
        ),
      )}}`,
    );
  }
  if (showTitle) {
    propStrs.push(`showTitle`);
  }
  if (showDescription) {
    propStrs.push(`showDescription`);
  }
 
  return `export default function Layout(props) {
  const { APIPage, OpenAPIPage } = props.components ?? {};
  // "APIPage" is the old name from v10, this allows both for backward compatibility
  const Comp = OpenAPIPage ?? APIPage;
  return (
    <>
      {props.children}
      <Comp ${propStrs.join(' ')} />
    </>
  );
}`;
}

Sources: packages/openapi/src/utils/pages/to-text.ts:153-207

Note

The APIPage component name is preserved from v10 for backward compatibility; if props.components.OpenAPIPage is undefined, the layout falls back to APIPage.

Sources: packages/openapi/src/utils/pages/to-text.ts:197-199

Frontmatter Formatting and Options

The generateDocument utility serializes YAML frontmatter using the js-yaml dump function after stripping undefined properties via removeUndefined. It appends auto-generated comments and user-defined MDX component imports.

export function generateDocument(
  frontmatter: unknown,
  content: string,
  options: PagesToTextOptions,
): string {
  const { addGeneratedComment = true, imports } = options;
  const out: string[] = [];
  const banner = dump(removeUndefined(frontmatter as object)).trimEnd();
  if (banner.length > 0) out.push(`---\n${banner}\n---`);
 
  if (addGeneratedComment) {
    let commentContent =
      'This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again.';
 
    if (typeof addGeneratedComment === 'string') {
      commentContent = addGeneratedComment;
    }
 
    commentContent = commentContent.replaceAll('/', '\\/');
    out.push(`{/* ${commentContent} */}`);
  }
 
  if (imports) {
    out.push(
      ...imports
        .map((item) => `import { ${item.names.join(', ')} } from ${doubleQuote(item.from)};`)
        .join('\n'),
    );
  }
 
  out.push(content);
  return out.join('\n\n');
}

Sources: packages/openapi/src/utils/pages/to-text.ts:107-139

Option KeyTypeDefault ValuePurpose
imports{ names: string[]; from: string; }[]undefinedAdditional imports of custom MDX components for the generated page.
frontmatter(title, description, context) => Record<string, unknown>undefinedCustomizes frontmatter fields; a full: true property is added by default.
includeDescriptionbooleanfalseControls whether description text is added to the document body instead of frontmatter.
addGeneratedCommentboolean | stringtrueAdds a comment at the top of generated files indicating they are auto-generated.

Sources: packages/openapi/src/utils/pages/to-text.ts:17-55

Warning

includeDescription is disabled by default because certain OpenAPI schemas contain invalid descriptions that break Markdown syntax during rendering.

Sources: packages/openapi/src/utils/pages/to-text.ts:38-44

Static Page File Generation

Disk Writing Workflow and Watch Mode

The generateFiles function manages the end-to-end process of writing generated documentation files to the local disk. It accepts a Config object, invokes generateFilesOnly to resolve output buffers, and recursively creates destination directories using node:fs/promises before writing each file.

export async function generateFiles(options: Config): Promise<void> {
  if (options.watch) {
    const { watch } = await import('chokidar');
    const subOptions: Config = { ...options, watch: false };
 
    await generateFiles(subOptions);
    const targets = options.input._getWatchPaths();
    console.log(`[Fumadocs OpenAPI] watching ${targets.join(', ')}`);
    watch(targets, {
      ignoreInitial: true,
    }).on('all', () => generateFiles(subOptions));
    return;
  }
 
  const files = await generateFilesOnly(options);
 
  await Promise.all(
    files.map(async (file) => {
      const filePath = path.join(options.output, file.path);
 
      await mkdir(path.dirname(filePath), { recursive: true });
      await writeFile(filePath, file.content);
      console.log(`Generated: ${filePath}`);
    }),
  );
}

Sources: packages/openapi/src/generate-file.ts:95-120

When options.watch is enabled, generateFiles spawns a chokidar file watcher targeting paths retrieved from options.input._getWatchPaths(). Whenever a schema file changes, the watcher triggers a regeneration cycle while setting watch: false on suboptions to prevent recursive watcher instantiation.

if (options.watch) {
  const { watch } = await import('chokidar');
  const subOptions: Config = { ...options, watch: false };
 
  await generateFiles(subOptions);
  const targets = options.input._getWatchPaths();
  console.log(`[Fumadocs OpenAPI] watching ${targets.join(', ')}`);
  watch(targets, {
    ignoreInitial: true,
  }).on('all', () => generateFiles(subOptions));
  return;
}

Sources: packages/openapi/src/generate-file.ts:96-107

Warning

The built-in watch flag in generateFiles is intended only for simple development cases; it ignores custom input functions and URLs, and it is recommended to configure chokidar manually for advanced setups.

Sources: packages/openapi/src/generate-file.ts:83-88

Manifest Generation and File Collection

The generateFilesOnly function iterates over schemas fetched from the OpenAPI server instance, creates auto presets via createAutoPreset, and transforms schema entries into OutputFile objects. It aggregates generated entries into a BeforeWriteContext containing generated record maps and generatedEntries records.

export async function generateFilesOnly(
  options: DistributiveOmit<Config, 'output'>,
): Promise<OutputFile[]> {
  const schemas = await options.input.getSchemas();
 
  const files: OutputFile[] = [];
  const generated: Record<string, OutputFile[]> = {};
  const generatedEntries: Record<string, OutputEntry[]> = {};
 
  const entries = Object.entries(schemas);
  if (entries.length === 0) {
    throw new Error('No input files found.');
  }
  const preset = createAutoPreset(options);
  for (const [id, schema] of entries) {
    const entries = fromSchema(id, schema.bundled, preset);
    const schemaFiles: OutputFile[] = [];
 
    generatedEntries[id] = entries;
    function scan(entry: OutputEntry) {
      if (entry.type === 'group') {
        for (const child of entry.entries) scan(child);
        return;
      }
 
      schemaFiles.push({
        path: entry.path,
        content: toText(entry, schema.bundled, options),
      });
    }
 
    for (const entry of entries) scan(entry);
    generated[id] = schemaFiles;
    files.push(...schemaFiles);
  }
 
  const context: BeforeWriteContext = {
    generated,
    generatedEntries,
  };
 
  if (options.index) {
    files.push(...writeIndexFiles(context, options));
  }
 
  if (options.meta) {
    files.push(...generateMeta(context, options.meta === true ? {} : options.meta));
  }
 
  await options.beforeWrite?.call(context, files);
  return files;
}

Sources: packages/openapi/src/generate-file.ts:122-173

If options.meta is configured, generateMeta emits meta.json manifests for content folders. The folderStyle option determines how nested groups are structured within the manifest's pages array.

function generateMeta(context: BeforeWriteContext, options: MetaOptions): OutputFile[] {
  const files: OutputFile[] = [];
  const folderStyle = options.folderStyle ?? 'folder';
 
  function scan(entries: OutputEntry[], parent?: OutputGroup) {
    const pages: string[] = [];
 
    for (const entry of entries) {
      const relativePath = PathUtils.slash(
        parent ? path.relative(parent.path, entry.path) : entry.path,
      );
 
      if (entry.type === 'group') {
        scan(entry.entries, entry);
 
        if (folderStyle === 'folder') {
          pages.push(relativePath);
        } else {
          pages.push(`---${entry.info.title}---`, `...${relativePath}`);
        }
      } else {
        pages.push(relativePath.slice(0, -path.extname(entry.path).length));
      }
    }
 
    if (pages.length === 0) return;
    files.push({
      path: parent ? path.join(parent.path, 'meta.json') : 'meta.json',
      content: JSON.stringify(
        {
          title: parent?.info.title,
          description: parent?.info.description,
          pages,
        },
        null,
        2,
      ),
    });
  }
 
  for (const entries of Object.values(context.generatedEntries)) {
    scan(entries);
  }
 
  return files;
}

Sources: packages/openapi/src/generate-file.ts:175-220

Option KeyTypeDefault ValuePurpose
inputOpenAPIServerRequiredThe OpenAPI server object providing schema definitions.
outputstringRequiredTarget output directory path on the file system.
indexIndexConfigundefinedConfiguration for generating index files with cards linking to generated pages.
metaboolean | MetaOptionsundefinedEnables or configures meta.json file generation for navigation structures.
beforeWrite(files: OutputFile[]) => void | Promise<void>undefinedLifecycle hook to add, change, or remove output files before disk persistence.

Sources: packages/openapi/src/generate-file.ts:47-74

Note

When folderStyle is set to 'separator' in MetaOptions, group entries emit custom title headers and path references using the format ---title--- and ...relativePath in the generated meta.json file.

Sources: packages/openapi/src/generate-file.ts:177-195

Was this page helpful?