Skip to main content
doc0blade47/fumadocsdocumentation home
Sign in

EPUB Book Export

Last updated Oct 12, 2026100% grounded
Relevant source files

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

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.

ParameterDefault Value / FallbackDescription
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.
cssdefaultEpubStylesCustom stylesheet string or default EPUB styles.
publicDirpath.resolve('public')Public directory path for resolving absolute asset references.
prependChapterTitlestrueHardcoded boolean flag passed to epubOptions.
numberChaptersInTOCtrueHardcoded 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 NameTypeDescription
titlestringBook title for the generated EPUB archive.
authorstring | string[]Author name or list of author names.
descriptionstringBook description metadata.
languagestringLanguage code for the document (e.g., 'en').
publisherstringPublisher metadata string.
coverstringCover image reference (URL, absolute file path, or public-relative path).
outputPathstringOptional file system destination path to write the EPUB buffer.
includePages(page: Config['page']) => booleanInitial inclusion filter predicate applied to pages.
excludePages(page: Config['page']) => booleanExclusion filter predicate applied after includePages.
cssstringCustom CSS stylesheet string for the EPUB package.
publicDirstringRoot public directory path used for resolving absolute asset paths.
sourceLoaderOutput<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

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 FormatResolution BehaviorExample 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 pathResolved 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 OptionValidation and Build Pathing Behavior
nextValidates 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-startValidates against framework path rules; requires manual prerender route configuration targeting standard build paths.
react-routerValidates against framework path rules; requires manual prerender route configuration targeting standard build paths.
wakuValidates 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

Was this page helpful?