Fix tab names that consist of a single JSX element
With parseMdx enabled, remarkCodeTab unwrapped a tab name that was entirely one JSX element and kept only its children, so icon-only names like tab="<Home />" rendered an empty trigger. Only paragraphs are unwrapped now, matching the behavior of fumadocs-core.
Fix OpenAPI 3.0 example in external files crashing OpenAPIPage
The version upgrader ran after external documents were embedded under x-ext, where it can no longer classify schemas by their JSON path: a schema-level example from an external 3.0 file became an Example Object map instead of the JSON Schema examples array, crashing the schema UI with schema.examples is not iterable.
Each document is now upgraded before bundling embeds it. This also honors the external file's own declared OpenAPI version, so a 3.0 file referenced from a 3.1 document is upgraded too (previously it was skipped entirely).
Support OpenAPI 3.2 tag hierarchy in groupBy: 'tag'
generateFiles now follows the tag hierarchy introduced in OpenAPI 3.2: a tag with a parent becomes a folder nested inside its parent tag's folder (including the generated meta.json), and tags with a kind other than nav no longer form groups, matching their intent (e.g. badge).
Operations referencing undeclared tags or having no tags are no longer dropped silently, which previously could produce an empty output directory. Undeclared tags now form their own group, and untagged operations are grouped under an unknown folder with a warning.
Fix OpenAPI 3.0 example in external files crashing OpenAPIPage
The version upgrader ran after external documents were embedded under x-ext, where it can no longer classify schemas by their JSON path: a schema-level example from an external 3.0 file became an Example Object map instead of the JSON Schema examples array, crashing the schema UI with schema.examples is not iterable.
Each document is now upgraded before bundling embeds it. This also honors the external file's own declared OpenAPI version, so a 3.0 file referenced from a 3.1 document is upgraded too (previously it was skipped entirely).
Remark LLMs: export a component with output: "function"
With output: "function", _markdown becomes a component instead of a string: Markdown content is still stringified at compile time, while JSX elements stay as JSX, receiving their original props.
Render it with renderToMarkdown from fumadocs-core/server. Elements resolve from props.components: a component can call asMarkdown() to output its own Markdown form, other components (including missing ones) are serialized as JSX syntax.
import { renderToMarkdown } from 'fumadocs-core/server';const { _markdown: Content } = await page.data.load();const text = await renderToMarkdown(<Content components={getMDXComponents()} />);
getText('processed') keeps working: it renders the component for you, with an optional components map:
const text = await page.data.getText('processed', { components: getMDXComponents() });
Supported in bundler collections with both compilers, and in dynamic: true collections & @fumadocs/satteri/local-md with the Sätteri compiler.
Remark LLMs: export a component with output: "function"
With output: "function", _markdown becomes a component instead of a string: Markdown content is still stringified at compile time, while JSX elements stay as JSX, receiving their original props.
Render it with renderToMarkdown from fumadocs-core/server. Elements resolve from props.components: a component can call asMarkdown() to output its own Markdown form, other components (including missing ones) are serialized as JSX syntax.
import { renderToMarkdown } from 'fumadocs-core/server';const { _markdown: Content } = await page.data.load();const text = await renderToMarkdown(<Content components={getMDXComponents()} />);
getText('processed') keeps working: it renders the component for you, with an optional components map:
const text = await page.data.getText('processed', { components: getMDXComponents() });
Supported in bundler collections with both compilers, and in dynamic: true collections & @fumadocs/satteri/local-md with the Sätteri compiler.
Slice Markdown output from the authored source
remarkLlms and the stringify mode of remarkStructure no longer re-stringify the document with mdast-util-to-markdown, they share a stringifier that slices the original source instead. On a 40 kB document with the default preset, remarkLlms went from 45 ms to 11 ms per compile, and structured data with stringify from 34 ms to 8 ms.
For remarkLlms, the output now matches the authored document: heading IDs are appended, frontmatter and ESM nodes are dropped, remarkInclude splices in the included content, and package-manager tabs, images and admonitions appear as written instead of their expanded <Tabs> / <img> / <Callout> forms. Heading levels also no longer lose their # markers. Where generated content replaces the authored form, the output shows the generated content: remarkAutoTypeTable renders its type tables as Markdown tables, instead of the meaningless <auto-type-table> tag.
For remarkStructure, content records keep the authored inline syntax, while links and JSX elements are flattened into their plain text. stringify now takes true or a filterElement callback for the elements whose syntax should be kept:
Elements inserted by plugins (e.g. remarkAutoTypeTable) have no source to slice, kept ones are reconstructed from their attributes so they stay searchable.
The mdast-util-to-markdown based options of both plugins (filterElement variants returning children-only, stringify functions, handlers) are removed, along with the mdast-util-gfm and mdast-util-mdx dependencies. Use the plugins of fumadocs-core if you need custom stringification.
Support HTTP Basic client authentication in OAuth password flow
Some OAuth servers require client credentials in an HTTP Basic Authorization header instead of the request body. The password flow dialog now offers a Client Authentication select to choose between the two methods, as described in RFC 6749, section 2.3.1.
Support HTTP Basic client authentication in OAuth password flow
Some OAuth servers require client credentials in an HTTP Basic Authorization header instead of the request body. The password flow dialog now offers a Client Authentication select to choose between the two methods, as described in RFC 6749, section 2.3.1.
Support HTTP Basic client authentication in OAuth password flow
Some OAuth servers require client credentials in an HTTP Basic Authorization header instead of the request body. The password flow dialog now offers a Client Authentication select to choose between the two methods, as described in RFC 6749, section 2.3.1.
Generated pages are grouped in a directory named after the root module. Set groupBy: 'none' to place them at the root of the source instead, the root module becomes index.mdx:
Pages are compiled in memory with page.data.load(), render() includes the default MDX components of Fumadocs UI and fumadocs-python/components. Links between modules and classes are resolved from the loader the source is attached to, and loaderPlugin() badges generated pages in the page tree.
convert() shares the same page builder. write() takes the output directory directly, and keeps the package name in file paths:
Read structured data from page.data.structuredData()
Search indexing no longer falls back to (await page.data.load()).structuredData. Runtime content sources expose structuredData() on page data instead, sharing the compile with load():
The renderer returned by load() still carries structuredData, existing code keeps working.
@fumadocs/base-ui@16.15.1
fumadocs-core@16.15.1
fumadocs-ui@16.15.1
Forward dynamic loader from fumadocs-core/source
Read structured data from page.data.structuredData()
Search indexing no longer falls back to (await page.data.load()).structuredData. Runtime content sources expose structuredData() on page data instead, sharing the compile with load():
The renderer returned by load() still carries structuredData, existing code keeps working.
Read structured data from page.data.structuredData()
Search indexing no longer falls back to (await page.data.load()).structuredData. Runtime content sources expose structuredData() on page data instead, sharing the compile with load():