---
title: "Docs Engine"
description: "Serve a @farming-labs/docs-powered docs runtime from Farm config, including human pages and agent-readable API routes."
canonical_url: "https://farmjs.dev/docs/docs-engine"
markdown_url: "https://farmjs.dev/docs/docs-engine.md"
last_updated: "2026-11-03"
x_farming_labs_generated_preamble: true
---

# Docs Engine
URL: /docs/docs-engine
LLM index: /llms.txt
Description: Serve a @farming-labs/docs-powered docs runtime from Farm config, including human pages and agent-readable API routes.

# Docs Engine

Serve a @farming-labs/docs-powered docs runtime from Farm config, including human pages and agent-readable API routes.

Farm supports the built-in docs renderer and edge-capable external adapters on Node.js and edge
deployment targets. Edge production builds compile the docs tree into a runtime-neutral content
snapshot, so Workers serve HTML, client-navigation data, Markdown, search, sitemap, robots, and
agent-discovery routes without filesystem access.

## Enable docs

The docs runtime renders with `@farming-labs/docs`, which the app installs:

```bash
pnpm add @farming-labs/docs
```

When docs are enabled (in `farm.config.ts`, or by a discovered `docs.config.*` or `docs.json`) and
the package is missing, `farm dev` and `farm build` stop with that install command. The React
starters already declare it.

**farm.config.ts**

```ts
import { defineConfig } from "@farm.js/core";

export default defineConfig({
  docs: {
    entry: "/docs",
    metadata: {
      description: "Product guides and API reference.",
    },
    nav: {
      title: "Acme Docs",
    },
  },
});
```

Farm uses `src/app/docs` for content when `entry` is `/docs`. Add markdown pages using the normal
app directory structure:

```txt
src/app/docs/
  page.md
  getting-started/
    page.md
  api-reference/
    page.md
```

Use frontmatter for page metadata:

```md
---
title: "Getting Started"
description: "Install Farm and create your first application."
---

# Getting Started
```

Every Markdown docs page also gets a page-specific 1200 by 630 social preview automatically. Farm
uses the title, description, section, and route to emit Open Graph and X metadata, then selects a
technical illustration suited to the page topic. The generated SVG is accessible, resolution
independent, content-fingerprinted, and served with immutable caching.

Use frontmatter only when a page needs to override the automatic result:

```md
---
title: "Authentication"
description: "Protect routes and verify sessions."
socialTitle: "Authentication in Farm.js"
socialDescription: "Typed, server-side authentication for full-stack React apps."
socialIllustration: "auth"
---
```

`socialIllustration` accepts `auth`, `cache`, `cli`, `integrations`, `project`, `routing`, or
`runtime`. Set `socialImage` to an absolute or root-relative image URL to supply a custom preview,
or set it to `false` to disable the preview for one page.

## Automatic docs routes

When `docs.entry` is enabled, Farm serves the docs entry and `/api/docs` machine endpoints
automatically. Route wrappers are only needed when you want to override the default behavior.

- /docs
- /docs/getting-started
- /docs/getting-started.md
- /api/docs?format=llms
- /api/docs?format=sitemap-xml
- /api/docs/agent/spec

`/api/docs?format=config` is public, so it returns only the documented docs config fields with
credentials replaced by `"[redacted]"`: values under `apiKey`, `token`, `secret`, `password`,
`authorization`, or `credential` keys, and every `headers` value. The server keeps the real values
for search, Ask AI, and Docs Cloud. `/api/docs?format=diagnostics` reports filesystem paths, so
only `farm dev` serves it; production answers 404.

### Private docs pages

Guard docs pages the way you guard app pages: a `middleware.ts` in the docs folder, a config
middleware `matcher` such as `["/docs/internal", "/docs/internal/:path*"]`, or an integration's
`protectedRoutes`. The guard decides who reads the page. The `/api/docs` endpoints, `llms.txt`,
`llms-full.txt`, and the sitemaps answer anyone, so they leave out every docs page a guard written
for its path covers: search skips it, its titles stay unlisted, and `/api/docs/<slug>.md` answers
not found. Guards that cover every path (a root `middleware.ts`, or middleware without a matcher,
with a catch-all matcher, or with a function matcher) name no page and hide nothing, the same rule
as the [sitemap](/docs/configuration#sitemap). A function matcher cannot be read at build time, so
give private docs a path matcher, or guard `/api/docs` and the llms files as well.

A page's `.md` URL is served only when its guard covers that URL too. `/docs/internal/:path*` never
meets `/docs/internal.md`, so that file answers 404; name it in the matcher to serve it to signed-in
readers. A docs response to a request with cookies or an `Authorization` header, or one middleware
attached request data to, is sent as `Cache-Control: private, no-store`, as app pages are, so a CDN
never hands a signed-in reader's copy to someone else.

Only a page's own URL reaches it: `/docs/internal//secret`, `/docs/Internal/secret`, or
`/api/docs?format=markdown&path=internal/./secret` answer not found rather than serving the file
under a URL its guard never sees.

An external docs adapter such as `@farming-labs/farmjs` serves pages, Markdown, `llms-full.txt`,
search, and MCP from its own index and never runs the app's guards. `farm dev` and `farm build`
stop with an error naming the pages when a guard covers a docs page and an adapter owns the docs
runtime. Move private pages out of the docs folder, or set `docs.adapter: false` to use Farm's
built-in docs runtime, which applies the rules above.

## Configure the docs experience

Keep docs configuration alongside the rest of the application in `farm.config.ts`:

**farm.config.ts**

```ts
import { defineConfig } from "@farm.js/core";

export default defineConfig({
  docs: {
    entry: "/docs",
    nav: {
      title: "Acme Docs",
      url: "/",
    },
    search: {
      provider: "simple",
      enabled: true,
      maxResults: 10,
    },
    socialImage: {
      baseUrl: "https://docs.example.com",
      siteName: "Acme",
      brand: "Acme",
    },
    pageActions: {
      copyMarkdown: {
        enabled: true,
      },
    },
    llmsTxt: {
      enabled: true,
      siteTitle: "Acme Docs",
    },
    sitemap: true,
    robots: true,
  },
});
```

The `docs` object controls the public path, metadata, navigation, search, page actions, agent output,
theme, icons, reading time, last-updated display, sitemap, and robots output. `defineDocs` is not
required for a Farm application.

### Built-in search

Farm uses the existing `DocsCommandSearch` React component from `@farming-labs/theme`, the same Omni
search used by the Next.js docs adapter. Farm provides its HTML mount and server endpoint; the shared
component owns the dialog, fuzzy ranking, filters, recent searches, loading and empty states, result
highlights, and keyboard navigation. The sidebar control and `Cmd+K` / `Ctrl+K` shortcut open it.

The component queries `/api/docs?query=<term>`. That endpoint returns the standard docs search result
array, so `provider`, `maxResults`, and custom provider behavior come from the same `docs.search`
config instead of a second client-side search implementation.

Use `search: true` for the built-in simple provider, or configure `provider: "simple"`,
`"algolia"`, `"typesense"`, `"mcp"`, or `"custom"`. Search result content is rendered as text, and
provider credentials remain on the server. Setting `search: false` or `search.enabled: false`
disables both the endpoint capability and the interface.

### Optional external config

Large documentation sites may move the serializable docs options into `docs.config.ts`,
`docs.config.js`, `docs.config.mjs`, `docs.config.cjs`, or `docs.json`. Farm discovers those files
automatically. Values declared in `farm.config.ts` take priority, so applications can still override
the entry or any shared setting.

## Several docs roots

One app can serve several independent docs sets, each under its own route, with its own content,
navigation, and search. Declare the extra sets once, in `roots` in the docs config file; the docs
config itself stays the first, main root.

**docs.config.tsx**

```tsx
import { defineDocs } from "@farming-labs/docs";

export default defineDocs({
  entry: "docs",
  nav: { title: "Acme Docs" },
  roots: [
    {
      entry: "statewire/docs",
      nav: { title: <span>Statewire</span> },
      metadata: { titleTemplate: "%s – Statewire" },
    },
    { entry: "pinboard/docs", contentDir: "content/pinboard", nav: { title: "Pinboard" } },
  ],
});
```

**farm.config.ts**

```ts
import { defineConfig } from "@farm.js/core";

export default defineConfig({
  docs: {
    entry: "/docs",
    // Optional: serializable overrides for one root, matched by entry.
    roots: [{ entry: "/statewire/docs", search: { enabled: false } }],
  },
});
```

- Each root inherits the main root's settings and replaces the fields it sets itself.
- `docs.roots` in `farm.config.ts` takes the same flat shape. An entry there merges over the docs
  config's root with the same `entry`, its fields winning, or adds a root of its own. From lowest to
  highest precedence: the docs config's main fields, the `docs` options in `farm.config.ts`, the
  docs config's root, and that root's `docs.roots` entry.
- `contentDir` defaults to `src/app/<entry>`, as it does for the main root, and is never inherited.
- Icons, React elements, MDX components, and callbacks in a root's docs config reach the adapter as
  written; Farm serializes only the plain values.
- `/sitemap.xml`, `/robots.txt`, `/llms-full.txt`, docs MCP, and the AI catalog entry come from the
  first root.
- Search follows the page it is opened from: it searches that page's root. `/api/docs?root=<entry>`
  or `?pathname=<page>` names the root explicitly.

Several roots need a docs adapter such as `@farming-labs/farmjs` on a Node.js deployment target,
Vercel included. `farm dev` and `farm build` stop with an error for the built-in renderer
(`docs.adapter: false`) and for edge targets. Each root's `entry` must be unique and cannot be `/`.

## Agent-readable output

The docs runtime can serve human pages and machine-readable output from the same markdown source:

- `.md` mirrors for individual pages.
- `llms.txt` style summary output.
- Sitemap output.
- Agent discovery/spec routes.
- Search metadata.

That means docs content only needs to be written once.

An app's own root `sitemap.ts`, `robots.ts`, `llms.ts`, or `llms-full.ts`
[metadata route](/docs/routing#application-metadata-routes), or the same file in `public/`, takes
`/sitemap.xml`, `/robots.txt`, `/llms.txt`, or `/llms-full.txt` from the docs runtime, and so does a
robots.txt generated by [`agent.crawlers`](/docs/configuration#ai-crawlers). The docs runtime keeps
its other output, such as `/sitemap.md` and `/.well-known/llms.txt`.

## Last updated dates

When `lastUpdated` is enabled, Farm uses the latest Git commit for each markdown page instead of
trusting deployment file timestamps. Production builds preserve those dates in the bundled docs
content, so archive or copy metadata cannot turn into a misleading footer date.

Set an explicit date in frontmatter when a page needs editorial control:

```md
---
title: "Release Policy"
lastModified: "2026-07-16"
---
```

Explicit frontmatter wins over generated Git metadata. When Git history is unavailable, local docs
fall back to the source file timestamp and copied production docs fall back to the build date.

## Override behavior

When `docs.entry` is enabled in `farm.config.ts`, Farm can mount docs pages and docs API routes automatically. Add explicit route wrappers only when the app wants to override default rendering, authentication, or response behavior.

```ts
// src/app/api/docs/route.ts
import { createDocsAPI } from "@farm.js/core/docs";

export const { GET, POST } = createDocsAPI();
```

Add the same exports to `src/app/api/docs/[...docs]/route.ts` when the override should also own
path-style machine routes. Most applications do not need either wrapper.

## Prerender docs pages

Docs pages render on request by default. With a docs adapter such as `@farming-labs/farmjs`, set
`docs.prerender` to have `farm build` render every page the adapter lists into a static file on
Node and serverless targets:

```ts
export default defineConfig({
  docs: {
    entry: "/docs",
    prerender: true,
  },
});
```

A docs page is a static file once prerendered, so it does not negotiate `Accept: text/markdown`;
agents use the `.md` URL the page links to. Markdown, search, `llms.txt`, and MCP keep rendering on
request. A page that middleware, a redirect, a rewrite, i18n, or a dynamic or cached route rule
covers keeps rendering on request, as for static app pages; `routeRules: { "/docs/changelog": {
prerender: false } }` keeps one page dynamic. Edge targets already serve docs pages from the build's
docs snapshot. Farm's built-in renderer (`docs.adapter: false`) and adapters that do not list their
pages keep rendering on request, with a build warning.

## Production notes

- Keep docs content in markdown so human pages and agent-readable pages stay in sync.
- Vercel functions bundle the docs content and last-modified metadata beside the server entry. Verify the emitted function without access to the source checkout so local files cannot hide a missing deployment asset.
- Keep the canonical docs configuration in the `docs` property of `farm.config.ts`.
- Use an external docs config only when a large navigation or theme definition is easier to maintain separately.
- Edge output snapshots docs content during `farm build`; rebuild the app after changing a page or docs configuration. `farm dev` continues to read source files live.
- Edge output supports Farm's built-in renderer (`docs.adapter: false`) and external adapters that publish an `edgeCompiler` entry. The current `@farming-labs/farmjs` adapter publishes that compiler automatically; applications do not configure it themselves. The capability is additive, so older Farm releases ignore it and retain their existing Node behavior.
- The official adapter's edge snapshot covers pages, client navigation, CSS, Markdown, search, sitemap, robots, and agent discovery. Set `mcp: false` and `telemetry: false` in the docs configuration because both features are enabled by default for production Node runtimes. Runtime-only adapter features such as docs MCP, Ask AI, feedback callbacks, analytics, telemetry, observability, and localized filesystem trees still require a Node target. Farm stops the build with the unsupported feature names instead of silently dropping them.
- Built-in and serializable hosted search providers run on edge targets. A function-backed custom search adapter requires a Node target because functions cannot be embedded in the content snapshot.
- Keep generated docs routes public unless product docs require auth.
- Verify docs build output before publishing package docs.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
