---
title: "EPUB Book Export"
description: "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, t..."
last_updated: "2026-10-12T07:53:59.736226+00:00"
canonical_url: "https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/developer-tooling/epub-book-export"
---

<details>
<summary>Relevant source files</summary>

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

- [packages/epub/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts)
- [packages/cli/src/commands/export-epub.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/commands/export-epub.ts)
- [packages/epub/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/package.json)
- [packages/epub/src/toc-builder.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/toc-builder.ts)
- [packages/asyncapi/src/generate-file.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/asyncapi/src/generate-file.ts)
- [packages/epub/src/types.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/types.ts)
- [packages/mdx/src/runtime/dynamic.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/runtime/dynamic.ts)
- [packages/cli/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/index.ts)
- [packages/openapi/src/utils/pages/builder.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/openapi/src/utils/pages/builder.ts)
- [packages/content/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/index.ts)
- [packages/preview/src/pages/...slugs.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/pages/%5B...slugs%5D.tsx)
- [packages/content/src/runtime.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/runtime.ts)
- [packages/asyncapi/src/utils/pages/builder.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/asyncapi/src/utils/pages/builder.ts)
- [packages/obsidian/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/obsidian/src/index.ts)
- [packages/asyncapi/src/server/index.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/asyncapi/src/server/index.tsx)
- [packages/python/src/convert.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/python/src/convert.ts)
- [packages/openapi/src/generate-file.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/openapi/src/generate-file.ts)
- [packages/create-app/src/bin.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/bin.ts)
- [packages/core/src/source/page-tree/builder.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/source/page-tree/builder.ts)
- [packages/preview/src/lib/source/storage.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/lib/source/storage.ts)
- [packages/epub/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/tsdown.config.ts)
- [packages/mdx/src/plugins/index-file.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/plugins/index-file.ts)
- [packages/core/src/mdx-plugins/rehype-toc.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/mdx-plugins/rehype-toc.ts)
- [packages/core/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/package.json)
- [packages/core/src/source/loader.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/source/loader.ts)
- [packages/asyncapi/src/utils/pages/preset-auto.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/asyncapi/src/utils/pages/preset-auto.ts)
- [packages/asyncapi/src/utils/pages/to-text.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/asyncapi/src/utils/pages/to-text.ts)
- [packages/mdx/src/runtime/server.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/runtime/server.ts)
- [packages/api-docs/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/api-docs/tsdown.config.ts)
- [packages/asyncapi/src/utils/pages/to-static-data.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/asyncapi/src/utils/pages/to-static-data.ts)
</details>

## 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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L1-L150), [packages/epub/package.json:1-62](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/package.json#L1-L62)

## 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](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/core-architecture/source-loader), converts raw markdown content into HTML, and packages the chapters into an EPUB buffer using `epub-gen-memory`.

```typescript
export async function exportEpub<C extends LoaderConfig>(
  options: EpubExportOptions<C>,
): Promise<Buffer>
```
Sources: [packages/epub/src/index.ts:64-66](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L64-L66)

The pipeline executes the following sequence of transformations to produce the final book:

```mermaid
sequenceDiagram
    participant Source as Source Loader
    participant Export as exportEpub
    participant Markdown as markdownToHtml
    participant EpubGen as epub-gen-memory
    
    Source->>Export: getPageTree() / getPages()
    Export->>Export: Apply include/exclude filters
    loop For each filtered page
        Export->>Markdown: Get raw text & convert to HTML
        Markdown-->>Export: HTML chapter content
    end
    Export->>EpubGen: epub(epubOptions, chapters)
    EpubGen-->>Export: EPUB Buffer
    Export->>Export: Write to outputPath (if defined)
```
Sources: [packages/epub/src/index.ts:93-149](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L93-L149)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L144-L149)

### 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](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/ui-layouts/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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L80-L90), [packages/epub/src/index.ts:124-135](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L124-L135)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L70-L78)

## 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.

```typescript
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/types.ts#L3-L26)

The extension interface `EpubExportOptions` combines `EpubConfig` with required source loader outputs and custom markdown retrieval hooks.

```typescript
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/types.ts#L28-L35)

| 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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/types.ts#L3-L35)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L102-L106)

### 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.

```typescript
// 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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L92-L100)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L116-L116)

## 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`.

```typescript
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/toc-builder.ts#L8-L25)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L98-L100)

### 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.

```typescript
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L102-L122)

## 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`).

```typescript
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L16-L21)

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`.

```typescript
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L26-L39)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L41-L43)

### 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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/epub/src/index.ts#L31-L39)

## CLI scaffolding command

### Command-line integration and options

The [Fumadocs CLI](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/developer-tooling/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.

```typescript
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/index.ts#L55-L69)

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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/commands/export-epub.ts#L109-L140), [packages/cli/src/index.ts:60-60](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/index.ts#L60-L60)

### 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.

```typescript
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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/commands/export-epub.ts#L79-L96)

> [!WARNING]
> If a Next.js [project structure](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/getting-started/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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/commands/export-epub.ts#L133-L140)

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.

```typescript
// 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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/commands/export-epub.ts#L172-L208)

> [!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](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/commands/export-epub.ts#L211-L218), [packages/cli/src/commands/export-epub.ts:237-240](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/commands/export-epub.ts#L237-L240)

## Related

- [Fumadocs CLI](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/developer-tooling/fumadocs-cli)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/llms.txt) for all pages in this wiki.
