---
title: "Create App Generator"
description: "The create-fumadocs-app package provides an automated CLI scaffolding tool to set up new documentation projects configured with Fumadocs, supporting frameworks including Next.js, Waku, React Router..."
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/create-app-generator"
---

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

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

- [packages/create-app/src/bin.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/bin.ts)
- [packages/create-app/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/index.ts)
- [packages/create-app/src/constants.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/constants.ts)
- [packages/mdx/src/next/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/next/index.ts)
- [packages/cli/src/commands/export-epub.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/commands/export-epub.ts)
- [packages/create-app/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/package.json)
- [packages/create-app/scripts/sync.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/scripts/sync.ts)
- [packages/mdx/src/vite/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/mdx/src/vite/index.ts)
- [packages/create-app/src/plugins/orama-cloud.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/orama-cloud.ts)
- [packages/create-app/src/plugins/ai.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/ai.ts)
- [packages/create-app/src/transform/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/index.ts)
- [packages/cli/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/index.ts)
- [packages/base-ui/src/provider/tanstack.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/base-ui/src/provider/tanstack.tsx)
- [packages/create-app-versions/package.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app-versions/package.json)
- [packages/typescript/src/lib/base.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/typescript/src/lib/base.ts)
- [packages/create-app/src/plugins/next-use-takumi.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/next-use-takumi.ts)
- [packages/create-app/src/plugins/biome.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/biome.ts)
- [packages/create-app/src/plugins/oxlint.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/oxlint.ts)
- [packages/radix-ui/src/provider/tanstack.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/radix-ui/src/provider/tanstack.tsx)
- [packages/openapi/src/generate-file.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/openapi/src/generate-file.ts)
- [packages/content/src/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/content/src/index.ts)
- [packages/create-app/src/plugins/eslint.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/eslint.ts)
- [packages/preview/src/pages/...slugs.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/pages/%5B...slugs%5D.tsx)
- [packages/create-app/src/transform/tanstack-start.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/tanstack-start.ts)
- [packages/cli/src/commands/add.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/commands/add.ts)
- [packages/create-app/src/plugins/biome.next.json](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/biome.next.json)
- [packages/cli/src/config.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/cli/src/config.ts)
- [packages/preview/src/cli/index.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/preview/src/cli/index.ts)
- [packages/create-app/tsdown.config.ts](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/tsdown.config.ts)
- [packages/core/src/framework/tanstack.tsx](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/core/src/framework/tanstack.tsx)
</details>

## Overview

The `create-fumadocs-app` package provides an automated CLI scaffolding tool to set up new documentation projects configured with Fumadocs, supporting frameworks including Next.js, Waku, React Router, and TanStack Start. It addresses the complexity of configuring build-time MDX compilation, router integration, layout providers, and peripheral developer tooling by orchestrating interactive prompt groups, template asset copying, workspace dependency version alignment, and AST-based code transformations. Design decisions center around modular template plugins and AST manipulation via `ts-morph` to programmatically inject search integrations, AI chat components, and linter configurations directly into generated project structures without manual boilerplate intervention.

Sources: [packages/create-app/src/bin.ts:1-60](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/bin.ts#L1-L60), [packages/create-app/src/index.ts:1-126](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/index.ts#L1-L126), [packages/create-app/src/constants.ts:34-80](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/constants.ts#L34-L80), [packages/create-app/src/transform/index.ts:1-174](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/index.ts#L1-L174)

## Interactive Prompts and Scaffolding Execution

### CLI Configuration and Argument Parsing

The CLI entry point uses `@commander-js/extra-typings` to define positional arguments and option flags before initiating interactive prompts. The root command accepts an optional `[name]` argument for the target project and configures flags for build options, templates, package managers, and tool integrations.

```typescript
const command = program
  .argument('[name]', 'the project name')
  .option('--src', '(Next.js only) enable `src/` directory')
  .option('--install', 'install packages automatically')
  .option('--no-git', 'disable auto Git repository initialization')
  .addOption(
    new Option(
      '--linter <name>',
      'configure a linter/formatter, ESLint is currently Next.js only.',
    ).choices(['eslint', 'oxlint', 'biome']),
  )
  .addOption(
    new Option('--search <name>', 'configure a search solution').choices(['orama', 'orama-cloud']),
  )
  .addOption(
    new Option('--og-image <name>', 'configure OG image generation').choices(['next-og', 'takumi']),
  )
  .addOption(
    new Option('--ai-chat <name>', 'configure AI chat').choices([
      'openrouter',
      'llmgateway',
      'inkeep',
    ]),
  )
  .addOption(
    new Option('--template <name>', 'choose a template').choices(
      templates.map((item) => item.value),
    ),
  )
  .addOption(
    new Option('--pm <name>', 'choose a package manager')
      .choices(managers)
      .default(getPackageManager()),
  );
```
Sources: [packages/create-app/src/bin.ts:21-54](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/bin.ts#L21-L54)

The command line options govern project parameters such as framework features, linters, search providers, and package managers.

| Option Flag | Argument Type | Choices / Default | Purpose |
| :--- | :--- | :--- | :--- |
| `--src` | boolean | `undefined` | Enable `src/` directory (Next.js only) |
| `--install` | boolean | `undefined` | Install packages automatically |
| `--no-git` | boolean | `undefined` | Disable auto Git repository initialization |
| `--linter <name>` | string | `eslint`, `oxlint`, `biome` | Configure a linter or formatter |
| `--search <name>` | string | `orama`, `orama-cloud` | Configure a search solution |
| `--og-image <name>` | string | `next-og`, `takumi` | Configure Open Graph image generation |
| `--ai-chat <name>` | string | `openrouter`, `llmgateway`, `inkeep` | Configure AI chat integration |
| `--template <name>` | string | Dynamic template list | Choose a starter template |
| `--pm <name>` | string | Detected package manager | Choose a package manager |

Sources: [packages/create-app/src/bin.ts:23-54](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/bin.ts#L23-L54)

> [!NOTE]
> When executing in a CI environment detected via `isCI`, interactive prompts are bypassed entirely, falling back to default values such as `untitled` for project name and `+next+fuma-docs-mdx` for the template.

Sources: [packages/create-app/src/bin.ts:65-76](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/bin.ts#L65-L76)

### Interactive Prompt Groups

Using `@clack/prompts`, the CLI prompts the user through a structured group flow if flags are omitted from the command line execution. Each prompt checks for existing configuration values or CI flags before rendering interactive selectors or text inputs.

```typescript
const options = await group(
  {
    name: async () => {
      if (defaultName) return defaultName;
      if (isCI) return 'untitled';

      return text({
        message: 'Project name',
        placeholder: 'my-app',
        defaultValue: 'my-app',
      });
    },
    template: async () => {
      if (config.template) return config.template;
      if (isCI) return '+next+fuma-docs-mdx';

      return select<Template>({
        message: 'Choose a template',
        initialValue: '+next+fuma-docs-mdx',
        options: templates,
      });
    },
    src: async ({ results }: { results: { template?: Template } }) => {
      if (config.src !== undefined) return config.src;
      if (isCI || !results.template?.startsWith('+next')) return false;

      return confirm({
        message: 'Use `/src` directory?',
        initialValue: false,
      });
    },
```
Sources: [packages/create-app/src/bin.ts:62-92](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/bin.ts#L62-L92)

The prompt sequence evaluates template selections conditionally. For instance, the `src` directory prompt is restricted to templates starting with `+next`, and the linter options differ between Next.js templates and alternative frameworks.

> [!WARNING]
> If a user cancels out of the Clack prompt group using the abort shortcut, the `onCancel` handler triggers `cancel('Installation Stopped.')` and immediately terminates the process with code `0`.

Sources: [packages/create-app/src/bin.ts:219-223](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/bin.ts#L219-L223)

### Project Directory Initialization and Template Execution

Once prompt inputs are gathered, the core `create` function from `packages/create-app/src/index.ts` initializes the destination directory, applies registered template plugins, mirrors template files, updates `package.json` configurations, and sets up dependencies or Git repositories.

```typescript
export async function create(createOptions: Options): Promise<void> {
  const {
    outputDir,
    plugins = [],
    packageManager = 'npm',
    initializeGit = false,
    installDeps = false,
    log = console.log,
  } = createOptions;

  let template = templates.find((item) => item.value === createOptions.template)!;
  for (const plugin of plugins) {
    template = (await plugin.template?.call({ dest: outputDir }, template)) ?? template;
  }

  const appDir = path.join(outputDir, template.appDir);
  const projectName = path.basename(outputDir);
  const pluginContext: TemplatePluginContext = {
    template,
    dest: outputDir,
    log,
    appDir,
  };

  await copy(path.join(sourceDir, 'template', template.value), outputDir, {
    rename(file) {
      file = file.replace('example.gitignore', '.gitignore');

      return template.rename?.(file) ?? file;
    },
  });

  const packageJsonPath = path.join(outputDir, 'package.json');
  let packageJson = await initPackageJson(projectName, packageJsonPath);
  for (const plugin of plugins) {
    packageJson = (await plugin.packageJson?.call(pluginContext, packageJson)) ?? packageJson;
  }
  await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));

  const readmePath = path.join(outputDir, 'README.md');
  let readme = `# ${projectName}\n\n${await fs.readFile(readmePath)}`;
  for (const plugin of plugins) {
    readme = (await plugin.readme?.call(pluginContext, readme)) ?? readme;
  }
  await fs.writeFile(readmePath, readme);

  for (const plugin of plugins) {
    await plugin.afterWrite?.call(pluginContext);
  }

  if (installDeps) {
    try {
      await autoInstall(packageManager, outputDir);
      log('Installed dependencies');
    } catch (err) {
      log(`Failed to install dependencies: ${err}`);
    }
  }

  if (initializeGit && (await tryGitInit(outputDir))) {
    log('Initialized Git repository');
  }
}
```
Sources: [packages/create-app/src/index.ts:64-126](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/index.ts#L64-L126)

> [!TIP]
> During template asset copying, template files named `example.gitignore` are automatically renamed to `.gitignore`, and any custom template rename hooks are executed to map source paths correctly.

Sources: [packages/create-app/src/index.ts:88-94](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/index.ts#L88-L94)

## Template Sync and Dependency Alignment

### Template Asset Mirroring and Filtering

The template synchronization script (`packages/create-app/scripts/sync.ts`) mirrors example applications from the repository workspace into package-internal template directories (`packages/create-app/template`). When executing `main`, the script iterates across all defined template mappings, clearing existing non-README files via `tinyglobby` before copying fresh files from the source example directory.

```typescript
const templates = [
  'react-router',
  'tanstack-start',
  'tanstack-start-spa',
  'react-router-spa',
  'waku',
  ['next', '+next+fuma-docs-mdx'],
  ['next-static', '+next+fuma-docs-mdx+static'],
];

async function rm(files: string[]) {
  await Promise.all(files.map((file) => fs.rm(file)));
}

async function main() {
  await Promise.all(
    templates.map(async (template) => {
      template = Array.isArray(template) ? template : [template, template];
      const inDir = path.join(examplesDir, template[0]);
      const outDir = path.join(templateDir, template[1]);

      await rm(
        await glob(['**/*', '!README.md'], {
          cwd: outDir,
          absolute: true,
        }),
      );

```
Sources: [packages/create-app/scripts/sync.ts:11-38](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/scripts/sync.ts#L11-L38)

The synchronization process relies on filter functions and file-renaming rules during asset copying. Directories such as `node_modules`, `dist`, `out`, and `build` are excluded, along with hidden files starting with a dot. Specific build artifacts and environment files like `next-env.d.ts`, `pages.gen.ts`, `routeTree.gen.ts`, `README.md`, and `tsconfig.tsbuildinfo` are filtered out, whereas `.gitignore` files are explicitly renamed to `example.gitignore` so they safely check in without affecting package publishing.

| Source Filename / Pattern | Filter / Rename Action | Target Treatment in Template |
| :--- | :--- | :--- |
| `node_modules`, `dist`, `out`, `build` | `filterDir` returns `false` | Excluded entirely from copy |
| Hidden directories/files (`.*`) | `filterDir` / `filter` returns `false` | Excluded unless explicitly allowed |
| `.gitignore` | `rename` maps to `example.gitignore`, `filter` returns `true` | Copied and renamed to prevent local git ignore collisions |
| `next-env.d.ts`, `pages.gen.ts`, `routeTree.gen.ts`, `README.md`, `tsconfig.tsbuildinfo` | `filter` returns `false` | Excluded from generated templates |

Sources: [packages/create-app/scripts/sync.ts:39-77](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/scripts/sync.ts#L39-L77)

> [!NOTE]
> Template definitions explicitly map source directories in `../../examples` to their respective CLI template identifiers, supporting Next.js, React Router, Waku, and TanStack Start configurations.

Sources: [packages/create-app/scripts/sync.ts:11-19](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/scripts/sync.ts#L11-L19)

### Workspace Package Version Replacement

When scaffolding a new project, the `initPackageJson` function in `packages/create-app/src/index.ts` reads the target `package.json` file, updates the project name, appends the `postinstall` script `fumadocs-mdx`, and replaces any workspace-protocol dependencies with explicit version numbers mapped from package constants.

```typescript
async function initPackageJson(
  projectName: string,
  packageJsonPath: string,
): Promise<PackageJsonType> {
  function replaceWorkspaceDeps(deps: Record<string, string> = {}) {
    for (const k in deps) {
      if (deps[k].startsWith('workspace:') && k in depVersions) {
        deps[k] = depVersions[k as keyof typeof depVersions];
      }
    }

    return deps;
  }

  const packageJson: PackageJsonType = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));

  return {
    ...packageJson,
    name: projectName,
    scripts: {
      ...packageJson.scripts,
      postinstall: 'fumadocs-mdx',
    },
    dependencies: replaceWorkspaceDeps(packageJson.dependencies),
    devDependencies: replaceWorkspaceDeps(packageJson.devDependencies),
  };
}
```
Sources: [packages/create-app/src/index.ts:128-154](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/index.ts#L128-L154)

The workspace dependency replacement helper (`replaceWorkspaceDeps`) scans dependency and devDependency records. Any entry whose version string starts with `workspace:` and whose package name exists in `depVersions` has its value overwritten by the resolved version constant.

> [!WARNING]
> If a dependency specifies a `workspace:` protocol prefix but its package name is missing from `depVersions`, the version string remains unmodified, which can cause package manager resolution failures during installation.

Sources: [packages/create-app/src/index.ts:132-140](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/index.ts#L132-L140)

### Dependency Constants and Version Tracking

Dependency versions are aggregated inside `packages/create-app/src/constants.ts` by combining external dependencies declared in `packages/create-app-versions/package.json` with workspace package versions extracted directly from core workspace package manifests.

```typescript
import { fileURLToPath } from 'node:url';
import versionPkg from '../../create-app-versions/package.json';
import * as corePkg from '../../core/package.json';
import * as mdxPkg from '../../mdx/package.json';
import * as radixPkg from '../../radix-ui/package.json';
import * as basePkg from '../../base-ui/package.json';

```
Sources: [packages/create-app/src/constants.ts:1-7](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/constants.ts#L1-L7)

The version aggregator iterates over workspace packages (`corePkg`, `mdxPkg`, `radixPkg`, `basePkg`) and injects their current versions into the `depVersions` record exported for scaffolding operations.

```typescript
const workspaces = [corePkg, mdxPkg, radixPkg, basePkg];

export const depVersions = versionPkg.dependencies;

for (const workspace of workspaces) {
  depVersions[workspace.name as keyof typeof depVersions] = workspace.version;
```
Sources: [packages/create-app/src/constants.ts:82-87](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/constants.ts#L82-L87)

The baseline versions defined in `packages/create-app-versions/package.json` supply default dependency specifiers for external tooling and libraries.

```json
"dependencies": {
  "@biomejs/biome": "^2.4.16",
  "@orama/core": "^1.2.19",
  "@takumi-rs/image-response": "^1.8.1",
  "fumadocs-core": "workspace:*",
  "fumadocs-mdx": "workspace:*",
  "fumadocs-ui": "workspace:*",
  "oxlint": "^1.69.0",
  "react": "^19.2.7"
}
```
Sources: [packages/create-app-versions/package.json:6-15](https://github.com/blade47/fumadocs-versions/blob/HEAD/package.json#L6-L15)

## Linter and Tooling Integrations

### ESLint, Biome, and Oxlint Plugins

Template plugins for quality and formatting tools automate both script injections and configuration file generation during project scaffolding. Each linter plugin hooks into the template lifecycle by mutating package manifests through `packageJson` and writing out configuration files in `afterWrite`.

| Plugin | Package / Config Target | Lint Script Command | Added Dev Dependencies |
| :--- | :--- | :--- | :--- |
| `eslint` | `eslint.config.mjs` | `eslint` | `eslint: ^9.39.4`, `eslint-config-next` |
| `biome` | `biome.json` | `biome check` | `@biomejs/biome` |
| `oxlint` | `.oxlintrc.json` | `oxlint` | `oxlint` |

Sources: [packages/create-app/src/plugins/eslint.ts:30-35](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/eslint.ts#L30-L35), [packages/create-app/src/plugins/biome.ts:15-20](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/biome.ts#L15-L20), [packages/create-app/src/plugins/oxlint.ts:53-57](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/oxlint.ts#L53-L57)

The ESLint plugin restricts execution strictly to Next.js templates by checking `this.template.value.startsWith('+next')`, writing an `eslint.config.mjs` configuration file that consumes `eslint-config-next/core-web-vitals` alongside global ignores for build directories.

Sources: [packages/create-app/src/plugins/eslint.ts:5-17](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/eslint.ts#L5-L17), [packages/create-app/src/plugins/eslint.ts:24-40](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/eslint.ts#L24-L40)

> [!WARNING]
> The ESLint plugin returns early without modifying package manifests or writing files if the selected template does not start with the `+next` prefix.

Sources: [packages/create-app/src/plugins/eslint.ts:24-40](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/eslint.ts#L24-L40)

The Biome plugin generates a `biome.json` configuration file by conditionally picking either `next` or `base` rulesets depending on the template prefix, and registers `lint` (`biome check`) and `format` (`biome format --write`) scripts.

Sources: [packages/create-app/src/plugins/biome.ts:13-17](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/biome.ts#L13-L17), [packages/create-app/src/plugins/biome.ts:25-27](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/biome.ts#L25-L27)

The Oxlint plugin configures `.oxlintrc.json` with schema validation pointing to `./node_modules/oxlint/configuration_schema.json`, enabling `typescript`, `react`, and `import` plugins by default, with `nextjs` added under Next.js template variants.

Sources: [packages/create-app/src/plugins/oxlint.ts:10-44](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/oxlint.ts#L10-L44), [packages/create-app/src/plugins/oxlint.ts:62-64](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/oxlint.ts#L62-L64)

### Next.js Takumi Asset Presets

For Next.js templates, the `nextUseTakumi` plugin swaps out standard image-generation bindings for the Takumi Rust-powered image response engine. When active, it injects `@takumi-rs/image-response` into project dependencies and applies three distinct file transformations during the `afterWrite` phase.

```typescript
async afterWrite() {
  if (this.template.value.startsWith('+next')) {
    await replaceImports(this);
    await replaceImagePath(this);
    await nextConfigExternal(this);
  }
},
```
Sources: [packages/create-app/src/plugins/next-use-takumi.ts:21-27](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/next-use-takumi.ts#L21-L27)

| Target File | Modification Method | Replacement Details |
| :--- | :--- | :--- |
| `app/og/docs/[...slug]/route.tsx` | `replaceAll` / `replace` | Substitutes `next/og` with `@takumi-rs/image-response`, updates UI import to `fumadocs-ui/og/takumi`, and adds `format: 'webp'` next to image dimensions. |
| `lib/source.ts` | `replaceAll` | Replaces occurrences of `image.png` with `image.webp`. |
| `next.config.mjs` | `replace` | Inserts `serverExternalPackages: ['@takumi-rs/image-response']` into the exported configuration object. |

Sources: [packages/create-app/src/plugins/next-use-takumi.ts:31-61](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/next-use-takumi.ts#L31-L61)

> [!NOTE]
> `nextConfigExternal` locates the `const config = {` anchor inside `next.config.mjs` and injects the server external packages array directly beneath it.

Sources: [packages/create-app/src/plugins/next-use-takumi.ts:50-60](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/next-use-takumi.ts#L50-L60)

## AST Route Manipulation for Frameworks

### TanStack Start Route and Prerender Configuration

Fumadocs utilizes `ts-morph` to inspect and programmatically manipulate TypeScript and JavaScript configuration files across different frameworks. When configuring project options like Orama Cloud search integrations, plugins modify route files and Vite or React Router configuration files in place.

Sources: [packages/create-app/src/transform/index.ts:1-12](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/index.ts#L1-L12), [packages/create-app/src/transform/tanstack-start.ts:1-3](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/tanstack-start.ts#L1-L3)

For TanStack Start templates, the transformation pipeline updates `vite.config.ts` to register prerendered search paths by analyzing the AST structure of the configuration file.

1. `main` invokes `oramaCloud` plugin execution during project generation.
Sources: [packages/create-app/src/plugins/orama-cloud.ts:37-49](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/orama-cloud.ts#L37-L49)
2. `oramaCloud` delegates to `tanstackStartRoutes` with a callback modifying route structures.
Sources: [packages/create-app/src/plugins/orama-cloud.ts:38-49](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/orama-cloud.ts#L38-L49)
3. `tanstackStartRoutes` calls `addTanstackPrerender` when `prerender` is set to true on a route option.
Sources: [packages/create-app/src/transform/index.ts:164-166](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/index.ts#L164-L166)
4. `addTanstackPrerender` evaluates `toItem` to format path objects for the prerender configuration array.
Sources: [packages/create-app/src/transform/tanstack-start.ts:19-21](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/tanstack-start.ts#L19-L21)
5. `getTanstackStartCall` locates the `tanstackStart` plugin call expression inside the default export of `vite.config.ts`.
Sources: [packages/create-app/src/transform/tanstack-start.ts:54-69](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/tanstack-start.ts#L54-L69)

```mermaid
sequenceDiagram
    participant orama-cloud.ts
    participant index.ts
    participant tanstack-start.ts
    orama-cloud.ts->>index.ts: tanstackStartRoutes(this, callback)
    index.ts->>tanstack-start.ts: addTanstackPrerender(configFile, [options.route])
    tanstack-start.ts->>tanstack-start.ts: getTanstackStartCall(sourceFile)
    tanstack-start.ts->>tanstack-start.ts: toItem(path)
```
Sources: [packages/create-app/src/plugins/orama-cloud.ts:37-49](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/orama-cloud.ts#L37-L49), [packages/create-app/src/transform/index.ts:151-174](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/index.ts#L151-L174), [packages/create-app/src/transform/tanstack-start.ts:9-49](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/tanstack-start.ts#L9-L49)

> [!CAUTION]
> If the `pages` property is missing from the options argument passed to the `tanstackStart` plugin call, `addTanstackPrerender` will dynamically add a new `pages` property containing the target routes.

Sources: [packages/create-app/src/transform/tanstack-start.ts:46-48](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/tanstack-start.ts#L46-L48)

### React Router Route Filtering and Normalization

React Router route updates modify both `routes.ts` and `react-router.config.ts`. The `reactRouterRoutes` wrapper normalizes path arguments by splitting segments and filtering out empty entries before matching or removing elements.

Sources: [packages/create-app/src/transform/index.ts:71-116](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/index.ts#L71-L116)

| Modification Utility | Target File | Action Performed |
| :--- | :--- | :--- |
| `addRoute` | `routes.ts` | Inserts a new route configuration into the route manifest. |
| `removeRoute` | `routes.ts` | Deletes the physical route entry file from disk and filters out matching entries. |
| `filterReactRouterPrerenderArray` | `react-router.config.ts` | Removes paths matching normalized patterns from `excluded` and `paths` prerender arrays. |

Sources: [packages/create-app/src/transform/index.ts:83-112](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/index.ts#L83-L112)

> [!IMPORTANT]
> When removing a route via `removeRoute`, path comparison relies on `normalizePath` which strips empty path segments to prevent mismatch errors caused by leading or trailing slashes.

Sources: [packages/create-app/src/transform/index.ts:79-99](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/transform/index.ts#L79-L99)

## Search and Registry Add-On Configuration

### Orama Cloud Integration and Sync Script Generation

The `oramaCloud` template plugin customizes scaffolded projects by injecting third-party search capabilities, adding `@orama/core` to package dependencies, and appending documentation notes to the project README.
Sources: [packages/create-app/src/plugins/orama-cloud.ts:8-29](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/orama-cloud.ts#L8-L29)

During the post-writing phase, `oramaCloud` copies template asset files to the destination and root provider directories, and injects search dialog components via `rootProvider`.
Sources: [packages/create-app/src/plugins/orama-cloud.ts:30-35](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/orama-cloud.ts#L30-L35)

It also handles framework-specific route generation for pre-rendered search indices by removing default API search endpoints and creating static JSON index routes across Next.js, TanStack Start, React Router, and Waku frameworks.
Sources: [packages/create-app/src/plugins/orama-cloud.ts:37-65](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/orama-cloud.ts#L37-L65)

| Template Target | Pre-rendered Static JSON Path |
| :--- | :--- |
| `+next+fuma-docs-mdx` | `.next/server/app/static.json.body` |
| `tanstack-start` | `.output/public/static.json` |
| `react-router` | `build/client/static.json` |
| `waku` | `dist/public/static.json` |

Sources: [packages/create-app/src/plugins/orama-cloud.ts:67-75](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/orama-cloud.ts#L67-L75)

Furthermore, `oramaCloud` generates a content synchronization script at `scripts/sync-content.ts` that reads the pre-rendered index and synchronizes records with Orama Cloud.
Sources: [packages/create-app/src/plugins/orama-cloud.ts:77-104](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/orama-cloud.ts#L77-L104)

> [!NOTE]
> The generated sync script automatically appends `&& bun scripts/sync-content.ts` to the project's build script in `package.json`.
Sources: [packages/create-app/src/plugins/orama-cloud.ts:13-16](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/orama-cloud.ts#L13-L16)

### AI Chat Component Installation and Environment Configuration

The `ai` plugin supports three distinct LLM providers: `openrouter`, `llmgateway`, and `inkeep`. It initializes component installations using the Fumadocs component installer connected to the remote registry.
Sources: [packages/create-app/src/plugins/ai.ts:10-25](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/ai.ts#L10-L25)

| Provider Key | Environment Variable Name |
| :--- | :--- |
| `openrouter` | `OPENROUTER_API_KEY` |
| `llmgateway` | `LLM_GATEWAY_API_KEY` |
| `inkeep` | `INKEEP_API_KEY` |

Sources: [packages/create-app/src/plugins/ai.ts:10-14](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/ai.ts#L10-L14)

After installing required dependencies, the plugin writes an empty environment variable key to `.env.local` and invokes `addAIChat` to inject AI search components into documentation layout files.
Sources: [packages/create-app/src/plugins/ai.ts:26-31](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/ai.ts#L26-L31)

Using `ts-morph`, `addAIChat` locates the `DocsLayout` JSX element within layout files across different frameworks and prepends the `<AISearch>` component tree containing `<AISearchPanel>` and `<AISearchTrigger>`.
Sources: [packages/create-app/src/plugins/ai.ts:39-87](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/ai.ts#L39-L87)

It then inserts import declarations for `AISearch`, `MessageCircleIcon`, `cn`, and `buttonVariants`.
Sources: [packages/create-app/src/plugins/ai.ts:89-106](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/ai.ts#L89-L106)

> [!WARNING]
> If `DocsLayout` cannot be matched as an opening JSX element child within the target source file, the AST traversal skips child injection entirely.
Sources: [packages/create-app/src/plugins/ai.ts:77-80](https://github.com/blade47/fumadocs/blob/a9f988897e7dc4ef3d62b2865d80da2f5607701e/packages/create-app/src/plugins/ai.ts#L77-L80)

## Related

- [Quick Start](https://www.doc0.dev/docs/e8872a72-1909-479c-b585-dc74c6e26726/technical/getting-started/quick-start)
- [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.
