Relevant source files
The following files were used as context for generating this wiki page:
- packages/epub/src/index.ts
- packages/cli/src/commands/export-epub.ts
- packages/epub/package.json
- packages/epub/src/toc-builder.ts
- packages/asyncapi/src/generate-file.ts
- packages/epub/src/types.ts
- packages/mdx/src/runtime/dynamic.ts
- packages/cli/src/index.ts
- packages/openapi/src/utils/pages/builder.ts
- packages/content/src/index.ts
- packages/preview/src/pages/...slugs.tsx
- packages/content/src/runtime.ts
- packages/asyncapi/src/utils/pages/builder.ts
- packages/obsidian/src/index.ts
- packages/asyncapi/src/server/index.tsx
- packages/python/src/convert.ts
- packages/openapi/src/generate-file.ts
- packages/create-app/src/bin.ts
- packages/core/src/source/page-tree/builder.ts
- packages/preview/src/lib/source/storage.ts
- packages/epub/tsdown.config.ts
- packages/mdx/src/plugins/index-file.ts
- packages/core/src/mdx-plugins/rehype-toc.ts
- packages/core/package.json
- packages/core/src/source/loader.ts
- packages/asyncapi/src/utils/pages/preset-auto.ts
- packages/asyncapi/src/utils/pages/to-text.ts
- packages/mdx/src/runtime/server.ts
- packages/api-docs/tsdown.config.ts
- packages/asyncapi/src/utils/pages/to-static-data.ts
Overview
Fumadocs provides EPUB book export capabilities via the fumadocs-epub package, allowing documentation collections to be packaged into portable e-reader books. By integrating with epub-gen-memory, the export pipeline processes documentation sources, flattens navigation page trees, resolves relative assets and cover images, and compiles markdown content into formatted EPUB buffers.
Sources: packages/epub/src/index.ts:1-150, packages/epub/package.json:1-62
Epub generation entrypoint
Programmatic export pipeline
The core export functionality is exposed through the exportEpub function in packages/epub/src/index.ts. This asynchronous function accepts an export configuration object, collects and filters documentation pages from the source loader, converts raw markdown content into HTML, and packages the chapters into an EPUB buffer using epub-gen-memory.
export async function exportEpub<C extends LoaderConfig>(
options: EpubExportOptions<C>,
): Promise<Buffer>Sources: packages/epub/src/index.ts:64-66
The pipeline executes the following sequence of transformations to produce the final book:
Sources: packages/epub/src/index.ts:93-149
Note
If the outputPath option is specified in EpubExportOptions, the resulting buffer is automatically written to disk using node:fs/promises with recursive directory creation enabled. Otherwise, the raw Buffer is simply returned to the caller.
Sources: packages/epub/src/index.ts:144-149
Default export configuration and fallbacks
The exportEpub function establishes defaults for missing metadata properties, styles, and paths. The configuration object passed to epub-gen-memory configures chapter numbering, table of contents titles, and asset resolvers.
| Parameter | Default Value / Fallback | Description |
|---|---|---|
author | 'anonymous' | Author string or array of authors; normalized to an array before passing to epub-gen-memory. |
language | 'en' | Two-letter language code assigned to the lang option. |
publisher | 'anonymous' | Publisher metadata string. |
css | defaultEpubStyles | Custom stylesheet string or default EPUB styles. |
publicDir | path.resolve('public') | Public directory path for resolving absolute asset references. |
prependChapterTitles | true | Hardcoded boolean flag passed to epubOptions. |
numberChaptersInTOC | true | Hardcoded boolean flag passed to epubOptions. |
tocTitle | 'Table of Contents' | Hardcoded TOC heading string passed to epubOptions. |
Sources: packages/epub/src/index.ts:80-90, packages/epub/src/index.ts:124-135
Warning
When getMarkdown is not explicitly provided in the options, the function attempts to invoke page.data.getText('raw'). If this call fails or the method is missing, a warning is logged to the console, and the page is skipped during chapter generation.
Sources: packages/epub/src/index.ts:70-78
Export configuration and schema
Type definitions and configuration options
The configuration schema for EPUB generation is defined through TypeScript interfaces in packages/epub/src/types.ts. The base EpubConfig interface accepts generic loader configurations to provide type safety across source collections, defining metadata properties, filtering predicates, and path resolutions.
export interface EpubConfig<Config extends LoaderConfig = LoaderConfig> {
/** Book title */
title: string;
/** Author name(s) */
author?: string | string[];
/** Book description */
description?: string;
/** Language code (e.g. 'en') */
language?: string;
/** Publisher name */
publisher?: string;
/** Cover image - URL, file path, or path relative to public dir */
cover?: string;
/** Output file path - if set, writes to file in addition to returning buffer */
outputPath?: string;
/** Filter: include only these pages. Applied first (only pages where includePages(page) === true are considered). */
includePages?: (page: Config['page']) => boolean;
/** Filter: exclude these pages. Applied after includePages (removes pages from the include-filtered set). */
excludePages?: (page: Config['page']) => boolean;
/** Custom CSS for the EPUB */
css?: string;
/** Public directory for resolving /public/... image paths */
publicDir?: string;
}Sources: packages/epub/src/types.ts:3-26
The extension interface EpubExportOptions combines EpubConfig with required source loader outputs and custom markdown retrieval hooks.
export interface EpubExportOptions<
Config extends LoaderConfig = LoaderConfig,
> extends EpubConfig<Config> {
/** Fumadocs source (from loader()) */
source: LoaderOutput<Config>;
/** function to get page Markdown content */
getMarkdown?: (page: Config['page']) => string | undefined | Promise<string | undefined>;
}Sources: packages/epub/src/types.ts:28-35
| Property Name | Type | Description |
|---|---|---|
title | string | Book title for the generated EPUB archive. |
author | string | string[] | Author name or list of author names. |
description | string | Book description metadata. |
language | string | Language code for the document (e.g., 'en'). |
publisher | string | Publisher metadata string. |
cover | string | Cover image reference (URL, absolute file path, or public-relative path). |
outputPath | string | Optional file system destination path to write the EPUB buffer. |
includePages | (page: Config['page']) => boolean | Initial inclusion filter predicate applied to pages. |
excludePages | (page: Config['page']) => boolean | Exclusion filter predicate applied after includePages. |
css | string | Custom CSS stylesheet string for the EPUB package. |
publicDir | string | Root public directory path used for resolving absolute asset paths. |
source | LoaderOutput<Config> | Fumadocs documentation source object generated via loader(). |
getMarkdown | (page: Config['page']) => string | undefined | Promise<string | undefined> | Custom asynchronous or synchronous function to retrieve raw page Markdown content. |
Sources: packages/epub/src/types.ts:3-35
Note
Page filtering executes in a strict two-stage sequence: includePages runs first to restrict the working set, followed immediately by excludePages which removes matching entries from the remaining subset.
Sources: packages/epub/src/index.ts:102-106
Source loader integration interfaces
The integration layer bridges Fumadocs core source loader outputs with export options by consuming types from fumadocs-core/source. When exportEpub executes, it extracts documentation through source.getPageTree() or falls back to source.getPages() if tree traversal yields no elements.
// Get pages in tree order (navigation order)
const pageTree = source.getPageTree();
const orderedPages = getPagesInTreeOrder(pageTree, (node) => source.getNodePage?.(node)).filter(
(p): p is Page => p != null,
);
// Fallback to getPages() if tree order yields no pages (e.g. i18n)
const pages = orderedPages.length > 0 ? orderedPages : source.getPages();
Sources: packages/epub/src/index.ts:92-100
Caution
If a page lacks a title property in its data object, the exporter falls back to utilizing the final segment of the page's URL slugs array, defaulting to 'Untitled' if both values are missing.
Sources: packages/epub/src/index.ts:116-116
Page tree flattening
Navigation tree traversal
The page tree traversal relies on getPagesInTreeOrder from packages/epub/src/toc-builder.ts to transform a Fumadocs navigation structure into a sequential array of pages. It executes a depth-first traversal of both main children and fallback children using flattenTree from fumadocs-core/page-tree.
export function getPagesInTreeOrder<Page>(
pageTree: PageTree.Root,
getNodePage: (node: PageTree.Item) => Page | undefined,
): Page[] {
const mainItems = flattenTree(pageTree.children);
const fallbackItems = pageTree.fallback ? flattenTree(pageTree.fallback.children) : [];
const items = [...mainItems, ...fallbackItems];
const pages: Page[] = [];
for (const item of items) {
const page = getNodePage(item);
if (page) {
pages.push(page);
}
}
return pages;
}Sources: packages/epub/src/toc-builder.ts:8-25
Note
If tree-order traversal returns an empty array due to internationalization structures or custom configurations, exportEpub falls back to using source.getPages() directly to populate the chapter collection.
Sources: packages/epub/src/index.ts:98-100
Chapter filtering and title resolution
Once ordered pages are collected, they undergo filtering via optional includePages and excludePages predicate functions before being converted into EPUB chapters. Each valid page retrieves its raw markdown content and maps its title through a fallback chain from page.data.title to the final slug segment.
const filteredPages = pages.filter((page) => {
if (includePages && !includePages(page)) return false;
if (excludePages && excludePages(page)) return false;
return true;
});
const chapters: { title: string; content: string }[] = [];
for (const page of filteredPages) {
const markdown = await getMarkdown(page);
if (!markdown) continue;
const pageDir = getPageDir(page, cwd);
const html = await markdownToHtml(markdown, pageDir, publicDir);
const pageTitle = page.data.title ?? page.slugs[page.slugs.length - 1] ?? 'Untitled';
chapters.push({
title: pageTitle,
content: html,
});
}Sources: packages/epub/src/index.ts:102-122
Asset and cover resolution
Path resolution functions
The exporter handles file system locations and resource URLs using dedicated helper utilities defined in packages/epub/src/index.ts. The getPageDir function determines the base directory for a given page by inspecting its absolutePath or falling back to resolving its relative path against the current working directory (cwd).
function getPageDir(page: Page, cwd: string): string {
if (page.absolutePath) {
return path.dirname(page.absolutePath);
}
return path.resolve(cwd, path.dirname(page.path));
}Sources: packages/epub/src/index.ts:16-21
Cover image references are normalized through resolveCoverPath, which supports absolute URLs, file schemes, public directory paths, and relative file paths. If a cover path begins with a slash, it resolves against the designated publicDir or falls back to a default public folder inside cwd.
function resolveCoverPath(
cover: string | undefined,
cwd: string,
publicDir?: string,
): string | undefined {
if (!cover) return undefined;
if (cover.startsWith('file://') || cover.startsWith('http://') || cover.startsWith('https://'))
return cover;
if (cover.startsWith('/')) {
const base = publicDir ?? path.join(cwd, 'public');
return pathToFileUrl(path.join(base, cover.slice(1)));
}
return pathToFileUrl(path.resolve(cwd, cover));
}Sources: packages/epub/src/index.ts:26-39
Note
File paths converted to URLs utilize pathToFileURL from Node's standard library to ensure cross-platform compatibility when passed to the EPUB generation memory backend.
Sources: packages/epub/src/index.ts:41-43
Cover path resolution schemes
| Input Prefix or Format | Resolution Behavior | Example Output / Destination |
|---|---|---|
file:// | Returned unmodified as a direct file URL. | file:///path/to/cover.png |
http:// / https:// | Returned unmodified as a remote network URL. | https://example.com/cover.png |
/ (Absolute public path) | Joined with publicDir (or cwd/public), stripped of the leading slash, and converted via pathToFileURL. | file:///path/to/project/public/cover.png |
| Relative path | Resolved directly against cwd and converted via pathToFileURL. | file:///path/to/project/assets/cover.png |
Sources: packages/epub/src/index.ts:31-39
CLI scaffolding command
Command-line integration and options
The Fumadocs CLI exposes the EPUB export utility under the export epub command via commander in packages/cli/src/index.ts. It accepts a required framework option along with output path and scaffolding configuration flags.
const exportCmd = program.command('export').description('export documentation to various formats');
exportCmd
.command('epub')
.description('export documentation to EPUB format (run after production build)')
.requiredOption('--framework <name>', 'React framework: next, tanstack-start, react-router, waku')
.option('--output <path>', 'output file path', 'docs.epub')
.option('--scaffold-only', 'only scaffold the EPUB route, do not copy')
.action(async (options: { output?: string; framework: string; scaffoldOnly?: boolean }) => {
await exportEpub({
output: options.output,
framework: options.framework,
scaffoldOnly: options.scaffoldOnly,
});
});Sources: packages/cli/src/index.ts:55-69
The command relies on framework identifiers defined during export execution. The supported framework values and their corresponding behavior are managed through validation routines in export-epub.ts.
| Framework Option | Validation and Build Pathing Behavior |
|---|---|
next | Validates Next.js configuration files (next.config.js, next.config.ts, next.config.mjs) or package dependencies (next) alongside app or pages directory presence. Scaffolds /export/epub/route.ts automatically. |
tanstack-start | Validates against framework path rules; requires manual prerender route configuration targeting standard build paths. |
react-router | Validates against framework path rules; requires manual prerender route configuration targeting standard build paths. |
waku | Validates against framework path rules; requires manual prerender route configuration targeting standard build paths. |
Sources: packages/cli/src/commands/export-epub.ts:109-140, packages/cli/src/index.ts:60-60
Route scaffolding and project detection
When executing exportEpub with the Next.js framework, the CLI inspects the working directory via findAppDir to locate either the app/ or src/app/ directory. If an existing route is absent at app/export/epub/route.ts, it provisions the endpoint using standard templates.
async function scaffoldEpubRoute(cwd: string): Promise<boolean> {
const appDir = await findAppDir(cwd);
if (!appDir) {
console.error(picocolors.red('Could not find app directory (app/ or src/app/)'));
return false;
}
const routePath = path.join(appDir, 'export', 'epub', 'route.ts');
if (await exists(routePath)) {
console.log(picocolors.yellow('EPUB route already exists at'), routePath);
return true;
}
await fs.mkdir(path.dirname(routePath), { recursive: true });
await fs.writeFile(routePath, API_ROUTE_TEMPLATE);
console.log(picocolors.green('Created EPUB route at'), routePath);
return true;
}Sources: packages/cli/src/commands/export-epub.ts:79-96
Warning
If a Next.js project structure cannot be verified via config files or dependency maps when --framework next is specified, the CLI terminates immediately with code 1 and outputs a project root error message.
Sources: packages/cli/src/commands/export-epub.ts:133-140
If the fumadocs-epub package is missing from project dependencies, the CLI attempts automatic installation using pnpm, bun, or npm depending on execution context. For Next.js deployments, the environment variable EXPORT_SECRET must be configured to authenticate requests against the running production server.
// Check for fumadocs-epub dependency
if (!pkg) {
console.error(
picocolors.red('Cannot read or parse package.json. Ensure it exists and is valid JSON.'),
);
process.exit(1);
}
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
if (!deps['fumadocs-epub']) {
console.log(picocolors.yellow('\nInstalling fumadocs-epub...'));
const packageManager = process.env.npm_execpath?.includes('pnpm')
? 'pnpm'
: process.env.npm_execpath?.includes('bun')
? 'bun'
: 'npm';
const installCmd = `${packageManager} add fumadocs-epub`;
try {
await execAsync(installCmd, { cwd });
} catch (err: unknown) {
const stderr =
err && typeof err === 'object' && 'stderr' in err
? String((err as { stderr?: string }).stderr)
: '';
console.error(picocolors.red(`Failed to install fumadocs-epub. Command: ${installCmd}`));
if (stderr) console.error(stderr);
process.exit(1);
}
}
if (framework === 'next') {
const secret = process.env.EXPORT_SECRET;
if (!secret) {
console.error(
picocolors.red('EXPORT_SECRET is required for Next.js export. Set it in your environment.'),
);
process.exit(1);
}Sources: packages/cli/src/commands/export-epub.ts:172-208
Caution
Network fetching of the EPUB binary from the Next.js endpoint enforces a strict 30-second timeout via AbortController, aborting the request and exiting with an error if the server fails to respond.
Sources: packages/cli/src/commands/export-epub.ts:211-218, packages/cli/src/commands/export-epub.ts:237-240
Related
On This Page
OverviewEpub generation entrypointProgrammatic export pipelineDefault export configuration and fallbacksExport configuration and schemaType definitions and configuration optionsSource loader integration interfacesPage tree flatteningNavigation tree traversalChapter filtering and title resolutionAsset and cover resolutionPath resolution functionsCover path resolution schemesCLI scaffolding commandCommand-line integration and optionsRoute scaffolding and project detectionRelated