API Reference
Complete reference for every option in docs.config.ts. All types are exported from @farming-labs/docs.
defineDocs(config)
The entry point for configuring your docs. Returns a typed config object.
import { defineDocs } from "@farming-labs/docs";
export default defineDocs({
entry: "docs",
// ...all options below
});DocsConfig
Top-level configuration object passed to defineDocs().
| Property | Type | Default | Description |
|---|---|---|---|
entry | string | required | URL path prefix for docs (e.g. "docs" → /docs) |
contentDir | string | same as entry | Path to content files. TanStack Start, SvelteKit, Astro, and Nuxt use it; Next.js uses app/{entry}/ |
staticExport | boolean | false | Set true for full static builds; hides search and AI (see Configuration) |
theme | DocsTheme | — | Theme preset from a factory (fumadocs(), darksharp(), pixelBorder(), etc.) |
github | string | GithubConfig | — | GitHub repo config for "Edit on GitHub" links |
nav | DocsNav | — | Sidebar header title and URL |
themeToggle | boolean | ThemeToggleConfig | true | Light/dark mode toggle |
breadcrumb | boolean | BreadcrumbConfig | true | Breadcrumb navigation |
sidebar | boolean | SidebarConfig | true | Sidebar visibility and customization |
icons | Record<string, unknown> | — | Shared icon registry for frontmatter icon fields and built-ins like Prompt |
components | Record<string, unknown> | — | Custom MDX component overrides, including built-ins like HoverLink and Prompt |
analytics | boolean | DocsAnalyticsConfig | false | Product/usage event stream for docs UI, search, AI, feedback, agent reads, and MCP usage |
observability | boolean | DocsObservabilityConfig | false | Trace event stream for Ask AI and MCP runs, including spans, timing, status, and errors |
onCopyClick | (data: CodeBlockCopyData) => void | — | Callback when the user clicks the copy button on a code block (runs in addition to copy) |
feedback | boolean | FeedbackConfig | false for UI | Human page feedback UI; agent feedback endpoints are default-on unless opted out |
agent | DocsAgentConfig | — | Defaults for docs agent compact and related machine-docs workflows |
readingTime | boolean | ReadingTimeConfig | false | Opt-in estimated reading-time label with per-page overrides |
pageActions | PageActionsConfig | — | "Copy Markdown" and "Open in LLM" buttons |
ai | AIConfig | — | RAG-powered AI chat |
search | boolean | DocsSearchConfig | true | Built-in simple search, Typesense, Algolia, or a custom adapter |
mcp | boolean | DocsMcpConfig | enabled | Built-in MCP server over stdio, /mcp, and /.well-known/mcp |
apiReference | boolean | ApiReferenceConfig | false | Generated API reference pages from supported framework route conventions or a hosted OpenAPI JSON |
sitemap | boolean | DocsSitemapConfig | true | Generated sitemap.xml, sitemap.md, and /.well-known/sitemap.md |
robots | boolean | DocsRobotsConfig | true | Runtime/generated robots.txt policy for docs and agent-readable routes |
changelog | boolean | ChangelogConfig | false | Generated changelog feed and entry pages from dated MDX entries |
ordering | "alphabetical" | "numeric" | OrderingItem[] | "alphabetical" | Sidebar page ordering strategy |
metadata | DocsMetadata | — | SEO metadata |
og | OGConfig | — | Open Graph image config |
components is merged into the default MDX component map, so you can both add your own components and replace built-ins such as Callout, Tabs, HoverLink, or Prompt. For built-in defaults like theme.ui.components.HoverLink and theme.ui.components.Prompt, see Creating themes. For usage examples and a live demo, see Components.
analytics and DocsAnalyticsConfig
Built-in product and usage event stream. Use it for page views, search activity, Ask AI request counts, feedback, page actions, machine-readable markdown reads, agent feedback routes, and MCP usage. See Analytics for the full guide and event list.
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true when object is passed | Enable or disable the analytics stream |
console | boolean | "log" | "info" | "debug" | true for analytics: true | Log analytics events to the console |
includeInputs | boolean | false | Include raw queries, AI questions, feedback comments, copied text |
onEvent | (event) => void | Promise<void> | — | Callback fired for every analytics event |
analytics: {
onEvent(event) {
console.info(event.type, event.properties);
},
}Inputs stay out unless requested
Search queries, AI questions, feedback comments, and copied content are omitted by default. Set
includeInputs: true only when your project is allowed to store that text.
observability and DocsObservabilityConfig
Built-in trace event stream for agent runtime debugging. Use it for Ask AI and MCP spans, timing, status, previews, errors, retries, and final handoffs. See Observability for the full guide and trace event list.
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true when object is passed | Enable or disable the observability stream |
console | boolean | "log" | "info" | "debug" | true for observability: true | Log trace events to the console |
includeInputs | boolean | false | Include raw input fields on custom observability events |
onEvent | (event) => void | Promise<void> | — | Callback fired for every observability trace event |
observability: {
console: "debug",
onEvent(event) {
console.info(event.type, event.traceId, event.durationMs);
},
}Separate from analytics
analytics.onEvent receives usage events like page_view, api_ai_request, and mcp_tool.
observability.onEvent receives trace events like run.start, model.call, and tool.result.
Ordering
Controls how pages are sorted in the sidebar. Three strategies are available:
"alphabetical" (default)
Pages are sorted alphabetically by folder name. No extra configuration needed.
ordering: "alphabetical","numeric"
Pages are sorted by the order field in frontmatter. Lower numbers appear first. Pages without order are sorted alphabetically after ordered pages.
ordering: "numeric",Then in your page frontmatter:
---
title: "Installation"
order: 1
---Slug-based (OrderingItem[])
Pass an array of { slug, children? } objects for explicit control over page order, including nested pages.
ordering: [
{ slug: "installation" },
{ slug: "cli" },
{ slug: "configuration" },
{
slug: "themes",
children: [
{ slug: "default" },
{ slug: "darksharp" },
{ slug: "pixel-border" },
{ slug: "creating-themes" },
],
},
{
slug: "customization",
children: [
{ slug: "colors" },
{ slug: "typography" },
{ slug: "sidebar" },
{ slug: "components" },
{ slug: "ai-chat" },
{ slug: "page-actions" },
],
},
{ slug: "reference" },
],OrderingItem
| Property | Type | Description |
|---|---|---|
slug | string | Folder name at this level (not the full path) |
children | OrderingItem[] | Ordering for child pages within this folder |
Pages not listed in the array appear alphabetically after the listed ones.
DocsNav
Sidebar header configuration.
| Property | Type | Default | Description |
|---|---|---|---|
title | string | ReactNode | "Docs" | Sidebar header title. React elements supported in Next.js and TanStack Start |
url | string | "/{entry}" | URL the title links to |
nav: {
title: "My Docs",
url: "/docs",
},LlmsTxtConfig
Generated llms.txt files for agent-readable docs indexes. The root files are enabled by default;
section files are opt-in and only add route handling plus discovery metadata.
If a native static file already exists at the same public route, such as public/llms.txt or
SvelteKit static/llms.txt, that file takes precedence and the generated output remains the
fallback.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable or disable llms.txt generation |
baseUrl | string | request origin at runtime | Base URL used for absolute links |
siteTitle | string | nav.title or "Documentation" | Heading shown in generated files |
siteDescription | string | undefined | Description shown below the heading |
maxChars | LlmsTxtMaxCharsConfig | { mode: "warn", chars: 50000 } | Budget for compact llms.txt files |
sections | LlmsTxtSectionConfig[] | [] | Optional section-level llms.txt files |
export default defineDocs({
llmsTxt: {
baseUrl: "https://docs.example.com",
maxChars: {
mode: "warn",
chars: 50_000,
},
sections: [
{
title: "API",
description: "Endpoint, SDK, and integration reference pages.",
match: "/docs/api/**",
maxChars: {
mode: "warn",
chars: 25_000,
},
},
],
},
});Default routes:
/llms.txt/llms-full.txt/.well-known/llms.txt/.well-known/llms-full.txt
For sections, the route is derived from the first match value. For example, /docs/api/**
creates /docs/api/llms.txt and /docs/api/llms-full.txt. Root /llms.txt lists configured
sections first and keeps matching pages inside those section files.
LlmsTxtMaxCharsConfig
| Property | Type | Default | Description |
|---|---|---|---|
mode | "warn" | "error" | "off" | "warn" | Warn, fail the response, or disable budget checks |
chars | number | 50000 | Maximum recommended character count |
LlmsTxtSectionConfig
| Property | Type | Description |
|---|---|---|
title | string | Section title shown in root and section files |
description | string | Optional description shown next to the section link |
match | string | string[] | Public URL matcher, such as /docs/api/** |
maxChars | LlmsTxtMaxCharsConfig | Optional section-specific budget |
DocsMcpConfig
Built-in MCP server configuration for AI clients, IDE agents, and local MCP tooling.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable the MCP server. MCP is on by default; set false to opt out. |
route | string | "/api/docs/mcp" | Streamable HTTP route used by the MCP endpoint |
name | string | nav.title or @farming-labs/docs | MCP server name reported to clients |
version | string | "0.0.0" | Version string reported to clients |
tools | DocsMcpToolsConfig | all enabled | Fine-grained tool toggles |
export default defineDocs({
entry: "docs",
mcp: {
route: "/api/docs/mcp",
name: "My Docs MCP",
tools: {
listPages: true,
getNavigation: true,
searchDocs: true,
readPage: true,
},
},
});DocsMcpToolsConfig
| Property | Type | Description |
|---|---|---|
listPages | boolean | Expose the list_pages tool |
getNavigation | boolean | Expose the get_navigation tool |
searchDocs | boolean | Expose the search_docs tool |
readPage | boolean | Expose the read_page tool |
Default MCP surface:
- Public HTTP route:
/mcp - Well-known HTTP route:
/.well-known/mcp - Canonical HTTP route:
/api/docs/mcp - stdio command:
pnpx @farming-labs/docs mcp - Built-in tools:
list_pages,get_navigation,search_docs,read_page
Framework notes:
- Next.js:
withDocs()auto-generates the default/api/docs/mcproute and public/mcpplus/.well-known/mcprewrites - TanStack Start / SvelteKit / Astro / Nuxt: use the generated public forwarder for MCP aliases, or add an equivalent framework hook/middleware that forwards to the built-in
MCPhandler - Custom routes: set
mcp.routeindocs.configand update the framework public forwarder so the configured path and the actual endpoint stay aligned
See MCP Server for the route snippets and examples.
DocsRobotsConfig
Runtime and generated robots.txt policy for docs and agent-readable routes. Server-rendered apps
serve /robots.txt through the shared docs handler when no static file already owns that path; the
CLI writes or appends a static file for static export and committed policies.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable or disable the robots policy |
path | string | public/robots.txt, or static/robots.txt for SvelteKit | File path used by docs robots generate |
baseUrl | string | sitemap.baseUrl or llmsTxt.baseUrl in the CLI | Public site URL used for the Sitemap: line |
ai | boolean | "allow" | "disallow" | "allow" | Explicitly allow or disallow common AI crawler user agents |
aiUserAgents | string | string[] | built-in common AI crawler list | Extra AI user-agent names to include beside the defaults |
extraRules | DocsRobotsRule[] | [] | Extra robots rule blocks appended after the generated agent policy |
export default defineDocs({
robots: {
enabled: true,
baseUrl: "https://docs.example.com",
path: "public/robots.txt",
ai: "allow",
},
});Generated policy covers the docs entry, .md routes, llms.txt, sitemap routes, AGENTS.md, skill.md, MCP
aliases, agent discovery routes, and common AI crawler user agents. Existing files are preserved by
default; use docs robots generate --append to add or update the managed block, or --force to
replace the file.
The agent discovery JSON advertises this surface as robots.enabled, robots.route, and
robots.defaultRoute, so agents can find the policy without guessing.
DocsRobotsRule
| Property | Type | Description |
|---|---|---|
userAgent | string | string[] | User-agent name or names for this rule block |
allow | string | string[] | Routes to allow |
disallow | string | string[] | Routes to disallow |
crawlDelay | number | Optional crawl-delay value emitted as-is |
See CLI for docs robots generate and Configuration for the
high-level setup guide.
DocsSearchConfig
Search is enabled by default. If you do not configure anything, the docs API uses the built-in simple adapter with section-based chunking.
export default defineDocs({
entry: "docs",
search: true,
});Built-in providers:
simple— zero-config docs search with section chunkingtypesense— external Typesense collection with optional hybrid modealgolia— external Algolia indexmcp— Streamable HTTP MCP endpoint that exposes asearch_docstoolcustom— your own adapter implementation
SimpleDocsSearchConfig
| Property | Type | Default | Description |
|---|---|---|---|
provider | "simple" | implied | Use the built-in search adapter |
enabled | boolean | true | Toggle search on or off |
maxResults | number | 10 | Maximum results to return |
chunking | DocsSearchChunkingConfig | { strategy: "section" } | Controls whether indexing happens per page or per section |
search: {
provider: "simple",
maxResults: 8,
chunking: {
strategy: "section",
},
},TypesenseDocsSearchConfig
| Property | Type | Default | Description |
|---|---|---|---|
provider | "typesense" | — | Use Typesense as the search backend |
baseUrl | string | — | Typesense API base URL |
collection | string | — | Collection name used for docs documents |
apiKey | string | — | Search API key |
adminApiKey | string | — | Optional admin key used for on-demand indexing |
maxResults | number | 10 | Maximum results to return |
syncOnSearch | boolean | true when adminApiKey is present | Re-index docs on the first search request |
queryBy | string[] | built-in fields | Custom Typesense query_by fields |
mode | "keyword" | "hybrid" | "keyword" | Keyword search only, or keyword + embeddings |
embeddings | DocsSearchEmbeddingsConfig | — | Semantic embeddings config for hybrid search |
chunking | DocsSearchChunkingConfig | { strategy: "section" } | Chunking strategy used before indexing/search |
search: {
provider: "typesense",
baseUrl: process.env.TYPESENSE_URL!,
collection: "docs",
apiKey: process.env.TYPESENSE_SEARCH_API_KEY!,
adminApiKey: process.env.TYPESENSE_ADMIN_API_KEY,
mode: "hybrid",
embeddings: {
provider: "ollama",
model: "embeddinggemma",
},
},pnpm dlx @farming-labs/docs search sync --typesenseAlgoliaDocsSearchConfig
| Property | Type | Default | Description |
|---|---|---|---|
provider | "algolia" | — | Use Algolia as the search backend |
appId | string | — | Algolia application id |
indexName | string | — | Algolia index name |
searchApiKey | string | — | Search API key |
adminApiKey | string | — | Optional admin key used for on-demand indexing |
maxResults | number | 10 | Maximum results to return |
syncOnSearch | boolean | true when adminApiKey is present | Re-index docs on the first search request |
chunking | DocsSearchChunkingConfig | { strategy: "section" } | Chunking strategy used before indexing/search |
search: {
provider: "algolia",
appId: process.env.ALGOLIA_APP_ID!,
indexName: "docs",
searchApiKey: process.env.ALGOLIA_SEARCH_API_KEY!,
adminApiKey: process.env.ALGOLIA_ADMIN_API_KEY,
},pnpm dlx @farming-labs/docs search sync --algoliaMcpDocsSearchConfig
| Property | Type | Default | Description |
|---|---|---|---|
provider | "mcp" | — | Use an MCP endpoint as the search backend |
endpoint | string | — | Streamable HTTP MCP endpoint |
headers | Record<string, string> | — | Extra headers sent to initialize and tool calls |
toolName | string | "search_docs" | MCP tool name used for search |
protocolVersion | string | "2025-11-25" | MCP protocol version header |
maxResults | number | 10 | Maximum results to request |
search: {
provider: "mcp",
endpoint: "/mcp",
},Relative endpoints are resolved against the current docs API request URL. That makes local setups like the Next example easy to test without hardcoding a host.
CustomDocsSearchConfig
| Property | Type | Description |
|---|---|---|
provider | "custom" | Use your own adapter |
adapter | DocsSearchAdapter | DocsSearchAdapterFactory | Search runtime implementation |
maxResults | number | Maximum results to return |
chunking | DocsSearchChunkingConfig | Chunking strategy used before your adapter runs |
import { createCustomSearchAdapter, defineDocs } from "@farming-labs/docs";
export default defineDocs({
entry: "docs",
search: createCustomSearchAdapter({
name: "my-search",
async search(query, context) {
return context.documents
.filter((doc) =>
`${doc.title} ${doc.section ?? ""} ${doc.content}`.toLowerCase().includes(query.query.toLowerCase()),
)
.slice(0, query.limit ?? 10)
.map((doc) => ({
id: doc.id,
url: doc.url,
content: doc.section ? `${doc.title} — ${doc.section}` : doc.title,
description: doc.description,
type: doc.type,
section: doc.section,
}));
},
}),
});DocsSearchAdapter
Custom adapters receive normalized source pages and chunked search documents so you can focus on retrieval and ranking instead of rebuilding the docs scan pipeline.
import type { DocsSearchAdapter } from "@farming-labs/docs";
export const adapter: DocsSearchAdapter = {
name: "my-search",
async search(query, context) {
return context.documents.map((doc) => ({
id: doc.id,
url: doc.url,
content: doc.section ? `${doc.title} — ${doc.section}` : doc.title,
description: doc.description,
type: doc.type,
section: doc.section,
}));
},
};DocsSearchChunkingConfig
| Property | Type | Default | Description |
|---|---|---|---|
strategy | "page" | "section" | "section" | Chunk by whole page or split by headings |
DocsSearchEmbeddingsConfig
| Property | Type | Default | Description |
|---|---|---|---|
provider | "ollama" | — | Embeddings provider for hybrid Typesense mode |
model | string | — | Embedding model id |
baseUrl | string | "http://127.0.0.1:11434" | Ollama base URL |
Notes:
search: falsedisables search entirely- Search requires the docs API route; static export hides the UI because there is no server route
- On Next.js, generated routes from
withDocs()passdocsConfigdirectly intocreateDocsAPI - MCP-backed search works with relative endpoints like
/mcpor/.well-known/mcpand absolute remote endpoints likehttps://docs.example.com/mcp - If MCP-backed search points at the same relative MCP route, the built-in
search_docstool falls back to simple search internally to avoid recursive loops - On custom/manual Next routes, import
createDocsAPIfrom@farming-labs/next/apiand pass the whole config:createDocsAPI(docsConfig)
@farming-labs/theme/api still works for now
@farming-labs/theme/api remains supported as a compatibility import path today, but prefer
@farming-labs/next/api in Next.js apps. The theme-level path will be deprecated.
ApiReferenceConfig
Generates API reference pages from framework route conventions or a hosted OpenAPI JSON document.
Use route scanning when the API lives in the same project. Use specUrl when the backend is
deployed elsewhere and already exposes an openapi.json.
Current support
apiReference is supported in Next.js, TanStack Start, SvelteKit, Astro, and
Nuxt.
Important
apiReference in docs.config controls scanning, theming, routeRoot, and exclude.
In Next.js, withDocs() also generates the route automatically.
In TanStack Start, SvelteKit, Astro, and Nuxt, you must additionally add the
framework route handler for /{path}.
Remote spec mode
specUrl is the easiest option when your docs app does not contain the backend route files.
Point it at a hosted openapi.json and keep the same themed API reference UI.
Agent schema discovery
When apiReference is enabled, the shared docs API also serves
GET /api/docs?format=openapi. The agent discovery spec advertises that OpenAPI schema URL, and
generated llms.txt / AGENTS.md / skill.md output points agents there before they scrape API reference pages.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true inside the object | Enable generated API reference pages |
path | string | "api-reference" | URL path where the generated API reference lives |
specUrl | string | — | Absolute URL to a hosted OpenAPI JSON document. When set, local route scanning is skipped |
routeRoot | string | "api" | Filesystem route root to scan. Bare values like "api" resolve inside app/ or src/app/; full values like "app/internal-api" and "src/app/v2/api" are supported |
exclude | string[] | [] | Route entries to omit from the generated reference. Accepts URL-style paths like "/api/hello" or route-root-relative values like "hello" / "hello/route.ts" |
apiReference: {
enabled: true,
path: "api-reference",
routeRoot: "api",
exclude: ["/api/internal/health", "internal/debug"],
},apiReference: {
enabled: true,
path: "api-reference",
specUrl: "https://petstore3.swagger.io/api/v3/openapi.json",
},When specUrl is set, routeRoot and exclude are ignored and the API reference is rendered
from the hosted spec instead.
This does not remove the framework route requirement on non-Next adapters. TanStack Start,
SvelteKit, Astro, and Nuxt still need their /{path} handler files so the docs app has a route
that serves the generated API reference page.
When output: "export" is used in Next.js, the generated API reference route is skipped automatically because it requires a server route handler.
Scanned route conventions:
- Next.js:
app/api/**/route.tsandsrc/app/api/**/route.ts - TanStack Start:
src/routes/api.*.tsand nested route files inside the configured route root - SvelteKit:
src/routes/api/**/+server.tsor+server.js - Astro:
src/pages/api/**/*.tsor.js - Nuxt:
server/api/**/*.tsor.js
Minimal route handlers for non-Next frameworks:
import { createFileRoute } from "@tanstack/react-router";
import { createTanstackApiReference } from "@farming-labs/tanstack-start/api-reference";
import docsConfig from "../../docs.config";
const handler = createTanstackApiReference(docsConfig);
export const Route = createFileRoute("/api-reference/")({
server: {
handlers: {
GET: handler,
},
},
});Create a second src/routes/api-reference.$.ts file with the same handler and
createFileRoute("/api-reference/$").
import { createSvelteApiReference } from "@farming-labs/svelte/api-reference";
import config from "$lib/docs.config";
export const GET = createSvelteApiReference(config);Create src/routes/api-reference/[...slug]/+server.ts with the same GET export.
import { createAstroApiReference } from "@farming-labs/astro/api-reference";
import config from "../../lib/docs.config";
export const GET = createAstroApiReference(config);Create src/pages/api-reference/[...slug].ts with the same GET export.
import { defineApiReferenceHandler } from "@farming-labs/nuxt/api-reference";
import config from "~/docs.config";
export default defineApiReferenceHandler(config);Create server/routes/api-reference/[...slug].ts with the same default export.
ChangelogConfig
Generates changelog listing and entry pages from dated MDX folders inside the docs content tree.
Current support
The turn-key generated changelog pages are currently wired in Next.js when you use
withDocs().
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true inside the object | Enable generated changelog pages |
path | string | "changelog" | URL path where the changelog listing lives inside the docs layout |
contentDir | string | "changelog" | Source directory under the docs content root. Example: app/docs/changelog/2026-03-04/page.mdx |
title | string | "Changelog" | Listing page title |
description | string | — | Listing page description shown in the header and metadata |
search | boolean | true | Show the built-in changelog search field |
actionsComponent | ReactNode | Component | — | Custom action content rendered in the changelog rail |
changelog: {
enabled: true,
path: "changelogs",
contentDir: "changelog",
title: "Changelog",
description: "Latest product updates and release notes.",
search: true,
},With entry: "docs" and the config above, the public pages render at:
/docs/changelogs/docs/changelogs/2026-03-04
When you use withDocs(), the route files are generated automatically. There is no separate
__changelog.generated.tsx file to maintain.
ChangelogFrontmatter
Optional frontmatter fields for each changelog entry:
| Property | Type | Description |
|---|---|---|
title | string | Entry title shown in the feed and entry page |
description | string | Short summary shown in the feed and metadata |
image | string | Cover image path or URL |
authors | string | string[] | Author name(s) shown in entry meta |
version | string | Compact version badge like v0.1.0 |
tags | string[] | Badge list shown in the feed and entry page |
pinned | boolean | Keep important entries pinned to the top of the listing |
draft | boolean | Hide the entry from generated routes and search indexes |
---
title: "OpenAPI mode is now the default"
description: "The docs example now ships with the faster API reference experience."
version: "v0.1.13"
tags: ["api-reference", "next"]
---GithubConfig
GitHub repository configuration. Enables "Edit on GitHub" links in page footers.
| Property | Type | Default | Description |
|---|---|---|---|
url | string | required | Repository URL (e.g. "https://github.com/my-org/repo") |
branch | string | "main" | Branch name |
directory | string | — | Content subdirectory for monorepos |
// Simple
github: "https://github.com/my-org/my-docs",
// Monorepo
github: {
url: "https://github.com/my-org/monorepo",
branch: "main",
directory: "apps/docs/docs",
},The "Edit on GitHub" link only appears when both
urlanddirectoryare provided.
onCopyClick and CodeBlockCopyData
Optional callback on DocsConfig. Fired when the user clicks the copy button on a code block — in addition to copying to the clipboard. Use it for analytics, logging, or custom behavior. See Code block copy callback for setup (Next.js / TanStack Start vs SvelteKit / Astro / Nuxt).
CodeBlockCopyData
Argument passed to onCopyClick(data: CodeBlockCopyData).
| Property | Type | Description |
|---|---|---|
title | string | undefined | Code block title (e.g. from fenced code meta), if present |
content | string | Raw code content (what was copied) |
url | string | Current page URL at the time of copy |
language | string | undefined | Language/syntax hint (e.g. "tsx", "bash"), if present |
import type { CodeBlockCopyData } from "@farming-labs/docs";
import { defineDocs } from "@farming-labs/docs";
export default defineDocs({
entry: "docs",
onCopyClick(data: CodeBlockCopyData) {
// data.title, data.content, data.url, data.language
console.log("Copied:", data.language, data.title ?? "(no title)");
},
});feedback, FeedbackConfig, AgentFeedbackConfig, and feedback payloads`
Optional docs page feedback UI. When enabled, a built-in "Good / Bad" prompt is rendered at the end of the page and emits a feedback payload when the user clicks one of the buttons.
FeedbackConfig
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Show or hide the feedback UI |
question | string | "How is this guide?" | Prompt shown above the buttons |
positiveLabel | string | "Good" | Label for the positive button |
negativeLabel | string | "Bad" | Label for the negative button |
onFeedback | (data: DocsFeedbackData) => void | — | Callback fired when the user clicks a button |
agent | boolean | AgentFeedbackConfig | true | Agent feedback routes served through /api/docs |
DocsFeedbackData
| Property | Type | Description |
|---|---|---|
value | "positive" | "negative" | Which feedback button the user clicked |
title | string | undefined | Current page title, when available |
description | string | undefined | Current page description, when available |
url | string | Full current page URL |
pathname | string | Current pathname without origin |
path | string | Alias of pathname |
entry | string | Docs entry root, e.g. "docs" |
slug | string | Page slug relative to the docs entry |
locale | string | undefined | Active locale when docs i18n is enabled |
import type { DocsFeedbackData } from "@farming-labs/docs";
import { defineDocs } from "@farming-labs/docs";
export default defineDocs({
entry: "docs",
feedback: {
enabled: true,
onFeedback(data: DocsFeedbackData) {
console.log("Feedback:", data.value, data.slug, data.url);
},
},
});This docs site persists feedback through a local Next.js route at
website/app/api/feedback/route.ts, backed by the DocsFeedback Prisma model in
website/prisma/schema.prisma.
See Page feedback for framework-specific notes. The built-in UI does not require a separate client bridge file.
AgentFeedbackConfig
Optional machine-facing feedback routes for agents, scripts, or evaluation flows.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable or disable the agent feedback endpoints |
route | string | "/api/docs/agent/feedback" | Public HTTP route for feedback submission |
schemaRoute | string | ${route}/schema | Public HTTP route for the machine-readable schema |
schema | Record<string, unknown> | built-in payload schema | Schema used to validate the payload object |
onFeedback | (data: DocsAgentFeedbackData) => Promise<void> | void | — | Async callback fired after a valid submission |
Notes:
- agent feedback routes are enabled by default; set
feedback: falseorfeedback: { agent: false }to opt out feedback.agentdoes not enable the human footer UI by itself- the request body always uses
{ context?, payload } - without
feedback.agent.onFeedback, valid submissions return{ ok: true, handled: false } - submitted feedback is not added to Ask AI prompts or docs search context by the framework
- in Next.js,
withDocs()adds the public route rewrites automatically - Astro, SvelteKit, TanStack Start, and Nuxt expose the same feedback contract through the shared
/api/docs?feedback=agentquery route - the shared
/api/docshandler is still the source of truth, so?feedback=agentalso works - agents should discover site identity, locale config, capability flags, search, active markdown routes,
Accept: text/markdown,Signature-Agentsupport, JSON-LD structured data,llms.txt, OpenAPI schema routes, sitemap routes,robots.txt,AGENTS.md,skill.md, skills, MCP, and feedback routes withGET /.well-known/agent.json;GET /.well-known/agentis the public fallback andGET /api/docs/agent/specis the canonical framework route
DocsAgentFeedbackContext
| Property | Type | Description |
|---|---|---|
page | string | undefined | Docs page path such as "/docs/installation" |
url | string | undefined | Full docs URL when available |
slug | string | undefined | Docs slug relative to the entry root |
locale | string | undefined | Active locale when docs i18n is enabled |
source | string | undefined | Arbitrary source label such as "md-route", "mcp", or "api" |
DocsAgentFeedbackData
| Property | Type | Description |
|---|---|---|
context | DocsAgentFeedbackContext | undefined | Optional docs/page transport context |
payload | Record<string, unknown> | Agent feedback payload validated by feedback.agent.schema |
import { defineDocs } from "@farming-labs/docs";
export default defineDocs({
entry: "docs",
feedback: {
agent: {
enabled: true,
route: "/api/docs/agent/feedback",
async onFeedback(data) {
console.log(data.context?.page, data.payload);
},
},
},
});{
"context": {
"page": "/docs/installation",
"source": "md-route"
},
"payload": {
"task": "install docs in an existing Next.js app",
"understanding": "partial",
"outcome": "implemented",
"confidence": 0.78
}
}Quick checks:
curl "http://127.0.0.1:3000/.well-known/agent.json"
curl "http://127.0.0.1:3000/AGENTS.md"
curl "http://127.0.0.1:3000/skill.md"
curl "http://127.0.0.1:3000/api/docs/agent/feedback/schema"
curl -X POST "http://127.0.0.1:3000/api/docs/agent/feedback" \
-H "content-type: application/json" \
-d '{"payload":{"task":"demo","outcome":"implemented"}}'ThemeToggleConfig
Light/dark mode toggle in the sidebar.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Show or hide the toggle |
default | "light" | "dark" | "system" | "system" | Forced theme when enabled: false |
mode | "light-dark" | "light-dark-system" | "light-dark" | Toggle behavior |
// Show toggle (default)
themeToggle: true,
// Hide toggle, follow system preference
themeToggle: false,
// Hide toggle, force dark mode
themeToggle: { enabled: false, default: "dark" },
// Show toggle with system option
themeToggle: { mode: "light-dark-system" },BreadcrumbConfig
Breadcrumb navigation above page content.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Show or hide breadcrumbs |
component | Component | — | Custom breadcrumb component (Next.js only) |
breadcrumb: true, // show (default)
breadcrumb: false, // hide
breadcrumb: { enabled: false }, // hideSidebarConfig
Sidebar visibility and customization. See Sidebar for the full guide including custom sidebar components.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Show or hide the sidebar |
component | (props: SidebarComponentProps) => ReactNode | — | Custom sidebar render function. Receives { tree, collapsible, flat }. Supported in Next.js and TanStack Start — other frameworks use slots. |
footer | ReactNode | — | Content rendered below nav items |
banner | ReactNode | — | Content rendered above nav items |
collapsible | boolean | true | Whether sidebar is collapsible on desktop |
flat | boolean | false | Render all items flat (Mintlify-style, no collapsible groups) |
folderIndexBehavior | "link" | "toggle" | "hidden" | adapter default | Whether folder parents with their own page navigate, only expand/collapse, or render as label-only groups |
folderIndexBehaviorOverrides | Record<string, "link" | "toggle" | "hidden"> | — | Selective per-folder overrides keyed by the folder landing-page URL |
Folder landing pages can still override both of those with frontmatter:
---
sidebar:
folderIndexBehavior: "toggle"
---SidebarComponentProps
Props passed to a custom sidebar component:
| Property | Type | Description |
|---|---|---|
tree | SidebarTree | Full page tree with all parent-child relationships |
collapsible | boolean | Whether folders are collapsible |
flat | boolean | Whether folders are rendered flat |
SidebarTree
| Property | Type | Description |
|---|---|---|
name | string | Root name (e.g. "Docs") |
children | SidebarNode[] | Top-level sidebar items |
SidebarNode
A union of SidebarPageNode | SidebarFolderNode.
SidebarPageNode
| Property | Type | Description |
|---|---|---|
type | "page" | Node type |
name | string | Display name |
url | string | Page URL |
icon | unknown | Icon from registry |
SidebarFolderNode
| Property | Type | Description |
|---|---|---|
type | "folder" | Node type |
name | string | Display name |
icon | unknown | Icon from registry |
index | SidebarPageNode | Folder's landing page |
children | SidebarNode[] | Child pages and sub-folders |
collapsible | boolean | Whether this folder collapses |
defaultOpen | boolean | Whether it starts open |
sidebar: true, // show (default)
sidebar: false, // hide
sidebar: { collapsible: false }, // non-collapsible
sidebar: { flat: true }, // Mintlify-style flat
sidebar: { folderIndexBehavior: "toggle" }, // all folder parents only toggle children
sidebar: {
folderIndexBehavior: "link",
folderIndexBehaviorOverrides: {
"/docs/components": "toggle",
"/docs/customization": "toggle",
},
},
sidebar: { // custom component
component: ({ tree }) => <MySidebar tree={tree} />,
},DocsTheme
Theme configuration returned by theme factories.
| Property | Type | Description |
|---|---|---|
name | string | Theme identifier (e.g. "fumadocs", "darksharp") |
ui | UIConfig | Visual configuration — colors, typography, layout |
Created via theme factories:
import { fumadocs } from "@farming-labs/theme";
import { darksharp } from "@farming-labs/theme/darksharp";
import { pixelBorder } from "@farming-labs/theme/pixel-border";
import { colorful } from "@farming-labs/theme/colorful";
theme: fumadocs({ ui: { /* overrides */ } }),import { fumadocs } from "@farming-labs/theme";
import { darksharp } from "@farming-labs/theme/darksharp";
import { pixelBorder } from "@farming-labs/theme/pixel-border";
import { colorful } from "@farming-labs/theme/colorful";
theme: fumadocs({ ui: { /* overrides */ } }),import { fumadocs, darksharp, pixelBorder } from "@farming-labs/svelte-theme";
import { colorful } from "@farming-labs/svelte-theme/colorful";
theme: fumadocs({ ui: { /* overrides */ } }),import { fumadocs } from "@farming-labs/astro-theme";
import { pixelBorder } from "@farming-labs/astro-theme/pixel-border";
import { darksharp } from "@farming-labs/astro-theme/darksharp";
import { colorful } from "@farming-labs/astro-theme/colorful";
theme: fumadocs({ ui: { /* overrides */ } }),import { fumadocs } from "@farming-labs/nuxt-theme/fumadocs";
import { colorful } from "@farming-labs/nuxt-theme/colorful";
theme: fumadocs({ ui: { /* overrides */ } }),UIConfig
Fine-grained visual configuration passed to theme factories.
| Property | Type | Description |
|---|---|---|
colors | ColorsConfig | Theme color tokens |
typography | TypographyConfig | Font families, heading styles |
radius | string | Global border-radius (e.g. "0px", "0.5rem") |
layout | LayoutConfig | Content width, sidebar width, TOC settings |
codeBlock | CodeBlockConfig | Syntax highlighting and line numbers |
sidebar | SidebarStyleConfig | Sidebar visual style |
card | CardConfig | Card component styling |
ColorsConfig
All color tokens. Accepts any CSS color value (hex, rgb, oklch, hsl).
| Token | Description |
|---|---|
primary | Primary brand color |
primaryForeground | Text color on primary backgrounds |
background | Page background |
foreground | Default text color |
muted | Muted background (e.g. code blocks) |
mutedForeground | Text on muted backgrounds |
border | Default border color |
card | Card background |
cardForeground | Card text color |
accent | Accent color |
accentForeground | Text on accent backgrounds |
secondary | Secondary color |
secondaryForeground | Text on secondary backgrounds |
popover | Popover/dropdown background |
popoverForeground | Popover text color |
ring | Focus ring color |
colors: {
primary: "oklch(0.72 0.19 149)",
background: "hsl(0 0% 2%)",
border: "#262626",
},LayoutConfig
| Property | Type | Default | Description |
|---|---|---|---|
contentWidth | number | — | Max width of content area in px |
sidebarWidth | number | — | Sidebar width in px |
tocWidth | number | — | Table of contents width in px |
toc.enabled | boolean | true | Show table of contents |
toc.depth | number | 3 | Max heading depth for TOC |
header.height | number | — | Header height in px |
header.sticky | boolean | true | Sticky header |
CodeBlockConfig
| Property | Type | Default | Description |
|---|---|---|---|
showLineNumbers | boolean | false | Show line numbers |
showCopyButton | boolean | true | Show copy button |
theme | string | — | Shiki theme for light mode |
darkTheme | string | — | Shiki theme for dark mode |
SidebarStyleConfig
| Property | Type | Default | Description |
|---|---|---|---|
style | "default" | "bordered" | "floating" | "default" | Sidebar visual style |
background | string | — | Background color override |
borderColor | string | — | Border color override |
TypographyConfig
typography: {
font: {
style: {
sans: "Inter, sans-serif",
mono: "JetBrains Mono, monospace",
},
h1: { size: "2.25rem", weight: 700, letterSpacing: "-0.02em" },
h2: { size: "1.75rem", weight: 600 },
h3: { size: "1.25rem", weight: 600 },
body: { size: "1rem", lineHeight: "1.75" },
small: { size: "0.875rem" },
},
},FontStyle
Used for each heading level and body text.
| Property | Type | Description |
|---|---|---|
size | string | CSS font-size (e.g. "2.25rem", "clamp(1.8rem, 3vw, 2.5rem)") |
weight | string | number | CSS font-weight (e.g. 700, "bold") |
lineHeight | string | CSS line-height (e.g. "1.2", "28px") |
letterSpacing | string | CSS letter-spacing (e.g. "-0.02em") |
AIConfig
RAG-powered AI chat. See Ask AI for usage guide.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable AI chat |
mode | "search" | "floating" | "search" | UI mode: integrated in search or floating widget |
position | "bottom-right" | "bottom-left" | "bottom-center" | "bottom-right" | Floating button position (only when mode: "floating") |
floatingStyle | "panel" | "modal" | "popover" | "full-modal" | "panel" | Floating chat visual style |
triggerComponent | Component | — | Custom floating button component |
model | string | "gpt-4o-mini" | LLM model name (OpenAI-compatible) |
systemPrompt | string | auto-generated | Custom system prompt |
baseUrl | string | "https://api.openai.com/v1" | OpenAI-compatible API base URL |
apiKey | string | process.env.OPENAI_API_KEY | API key for the LLM provider |
maxResults | number | 5 | Number of doc pages to include as RAG context |
useMcp | boolean | DocsAskAIMcpConfig | false | Route Ask AI retrieval through MCP search_docs without changing the search API |
suggestedQuestions | string[] | — | Pre-filled questions shown when chat is empty |
aiLabel | string | "AI" | Display name for the AI assistant |
feedback | boolean | DocsAskAIFeedbackConfig | true | Copy, like, and dislike action row for completed answers |
onActions | (data: DocsAskAIActionData) => void | Promise<void> | — | Callback for copy, like, and dislike response actions |
packageName | string | inferred from docs context | Optional package-name override for import examples |
docsUrl | string | — | Base URL the AI uses for links |
loader | string | "shimmer-dots" | Loading indicator variant ("shimmer-dots", "circular", "dots", "typing", "wave", "bars", "pulse", "pulse-dot", "terminal", "text-blink", "text-shimmer", "loading-dots") |
loadingComponent | (props: { name: string }) => ReactNode | — | Custom loading component (overrides loader, Next.js only) |
Floating styles
| Style | Description |
|---|---|
"panel" | Tall panel sliding up from the button. No backdrop. |
"modal" | Centered modal with backdrop overlay, like Cmd+K search. |
"popover" | Compact popover near the button. |
"full-modal" | Full-screen immersive overlay with pills for suggested questions. |
PageActionsConfig
Action buttons shown on each docs page. See Page Actions for usage guide.
| Property | Type | Default | Description |
|---|---|---|---|
position | "above-title" | "below-title" | "below-title" | Where to render the buttons |
alignment | "left" | "right" | "left" | Horizontal alignment of the action row |
copyMarkdown | boolean | CopyMarkdownConfig | false | "Copy Markdown" button |
openDocs | boolean | OpenDocsConfig | false | "Open in LLM" dropdown |
CopyMarkdownConfig
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Show the button |
OpenDocsConfig
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Show the dropdown |
providers | OpenDocsProvider[] | ChatGPT, Claude | LLM/tool providers |
OpenDocsProvider
| Property | Type | Description |
|---|---|---|
name | string | Display name (e.g. "ChatGPT", "Claude") |
icon | ReactNode | Icon rendered next to the name |
urlTemplate | string | URL template. {url} is replaced with the page URL, {mdxUrl} with the raw MDX source URL, and {githubUrl} with the GitHub edit URL when github is configured. Use {url}.md when you want the public machine-readable page route. |
promptUrlTemplate | string | Optional URL template used by the built-in Prompt component. {prompt} is replaced with the prompt text. Known providers like ChatGPT, Claude, Cursor, Gemini, and Copilot fall back to built-in prompt templates by name when this is omitted. |
pageActions: {
copyMarkdown: { enabled: true },
openDocs: {
enabled: true,
providers: [
{ name: "ChatGPT", urlTemplate: "https://chatgpt.com/?q={url}" },
{ name: "Claude", urlTemplate: "https://claude.ai/new?q=Read+this:+{url}" },
],
},
},ReadingTimeConfig
Estimated reading-time label shown on docs pages.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Show the computed read-time label when readingTime is configured |
wordsPerMinute | number | 220 | Word-count rate used for the estimate |
readingTime: {
enabled: true,
wordsPerMinute: 220,
},Notes:
readingTimeis disabled by default until you configure it inDocsConfig- the estimate strips code blocks, inline code, links, images, HTML, and URLs before counting
- page frontmatter can disable or override the value with
readingTime: false,readingTime: true, orreadingTime: 8, even when the global config is off - placement follows the page title slot: it sits directly below the page actions row when page actions are shown above or below the title
DocsMetadata
SEO metadata configuration. Page title and description also feed the generated Schema.org
JSON-LD structured data for each docs page.
| Property | Type | Default | Description |
|---|---|---|---|
titleTemplate | string | — | Page title template. %s is replaced with the page title. |
description | string | — | Default meta description |
twitterCard | "summary" | "summary_large_image" | — | Twitter card type |
metadata: {
titleTemplate: "%s – My Docs",
description: "Documentation for my project",
},Structured Data
Every docs page emits Schema.org JSON-LD automatically. The generated object uses
@type: "TechArticle" and includes the page title, description, canonical URL, dateModified,
and a BreadcrumbList.
No separate config flag is required. Absolute URLs are resolved from the first available public
base URL in sitemap.baseUrl, llmsTxt.baseUrl, robots.baseUrl, or ai.docsUrl; otherwise the
page route is emitted as a relative URL.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "Installation",
"description": "Install the framework",
"url": "https://docs.example.com/docs/installation",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://docs.example.com/docs/installation"
},
"dateModified": "2026-05-10T00:00:00.000Z",
"breadcrumb": {
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Docs",
"item": "https://docs.example.com/docs"
},
{
"@type": "ListItem",
"position": 2,
"name": "Installation",
"item": "https://docs.example.com/docs/installation"
}
]
}
}
</script>The agent discovery spec advertises this as capabilities.structuredData and
structuredData.schema: "https://schema.org/TechArticle".
OGConfig
Open Graph image configuration. When endpoint is set, each docs page gets a dynamic OG image: the framework passes the page’s title and description (from frontmatter) to the endpoint, and the layout injects the resulting image URL into og:image and twitter:image meta tags. For a full guide (how dynamic OG works, what context is passed, and how the docs website implements it), see OG Images.
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable OG image generation |
type | "static" | "dynamic" | — | Static images or dynamic generation |
endpoint | string | — | API endpoint for dynamic OG images |
defaultImage | string | — | Fallback OG image URL |
Example (Next.js): In docs.config.ts, set og.endpoint to your OG route path. Then implement app/api/og/route.tsx (or route.ts) that accepts title and description query params and returns a 1200×630 image (e.g. using ImageResponse from next/og).
og: {
enabled: true,
type: "dynamic",
endpoint: "/api/og",
},Testing your OG image
- Start the dev server for the app that serves the OG route (e.g.
cd website && pnpm devorcd examples/next && pnpm dev). - Open the OG URL in the browser, e.g.
http://localhost:3000/api/og?title=Test&description=My+description
(use your app’s port if different). - After changing the OG route code, refresh the page. If the image doesn’t update, do a hard refresh (e.g. Cmd+Shift+R on macOS, Ctrl+Shift+R on Windows) or open the URL in a private/incognito window. You can also add a cache-busting query param (e.g.
&_=1,&_=2) to force a new request.
Page Frontmatter
Each docs page (.mdx / .md) supports these frontmatter fields:
| Property | Type | Description |
|---|---|---|
title | string | Required. Page title, shown in sidebar and page heading |
description | string | Page description, used for meta tags and search |
related | string[] | Related doc URLs rendered as a comma-separated metadata line in .md routes and MCP page output |
agent | PageAgentFrontmatter | Per-page machine-docs metadata such as compaction token budgets |
sidebar | PageSidebarFrontmatter | Per-folder sidebar metadata such as folderIndexBehavior overrides |
readingTime | boolean | number | Disable the estimate for one page (false) or force an exact minute count |
icon | string | Icon key from the icons registry. Shows in sidebar. |
order | number | Sort order in the sidebar (only when ordering: "numeric"). Lower numbers first. |
tags | string[] | Tags for categorization |
ogImage | string | Shorthand for a single OG image path. Ignored if openGraph is set. |
openGraph | PageOpenGraph | Full Open Graph object (e.g. images: [{ url, width?, height? }]). When set, replaces generated OG from the config (e.g. dynamic endpoint). |
twitter | PageTwitter | Full Twitter card object (e.g. card, images). When set, replaces generated twitter metadata. |
hidden | boolean | If true, hide the page from the sidebar (page remains reachable by URL). Optional; behavior may vary by adapter. |
PageAgentFrontmatter
Page-level metadata for machine-readable docs workflows.
| Property | Type | Description |
|---|---|---|
tokenBudget | number | Approximate output token target for docs agent compact on this page |
---
title: "Installation"
description: "Install the framework"
agent:
tokenBudget: 777
---Notes:
agent.tokenBudgetoverrides globalagent.compact.maxOutputTokensdefaults and CLI--max-output-tokensfor that one page- if the page already has a sibling
agent.md,docs agent compactcompacts that file - if the page does not have
agent.md, the command compacts the generated machine-readable page output and writes a new siblingagent.md docs agent compact --changedonly processes docs pages changed in the current git working tree, including staged, unstaged, and untracked docs changesdocs agent compact --stale --include-missingwill also create missingagent.mdfiles for pages that defineagent.tokenBudget- inherited
minOutputTokensis clamped down totokenBudgetwhen needed so the compression API never receivesmin_output_tokens > max_output_tokens
PageSidebarFrontmatter
Page-level sidebar metadata used while building the docs navigation tree.
| Property | Type | Description |
|---|---|---|
folderIndexBehavior | "link" | "toggle" | "hidden" | Override how this folder page behaves in the default sidebar when it has child pages |
---
title: "Components"
sidebar:
folderIndexBehavior: "toggle"
---Notes:
- this is only read from folder landing pages such as
page.mdx/index.md - it overrides global
sidebar.folderIndexBehavior - it also overrides
sidebar.folderIndexBehaviorOverridesfor that same folder folderIndexBehavior: "hidden"keeps the folder route available but omits the folder index link from the sidebar tree; in flat mode this becomes a label-only group with child links only, and direct visits to the hidden parent route redirect to the first visible child
Static OG example — use openGraph and twitter in frontmatter to serve a static image instead of the dynamic OG endpoint:
---
title: "Title of Docs"
description: "Title of the docs goes here"
icon: "harddrive"
openGraph:
images:
- url: "/og/path-to-image/image.png"
width: 1200
height: 630
twitter:
card: "summary_large_image"
images:
- "/og/path-to-image/image.png"
---Minimal example (shorthand ogImage or generated OG):
---
title: "Getting Started"
description: "Set up your docs in 5 minutes"
related:
- /docs/installation
- /docs/customization/agent-primitive
readingTime: 4
icon: "rocket"
order: 1
---Data types
Frontmatter is YAML between the --- fences. Supported value types:
| Type | Example in frontmatter | Use in framework fields |
|---|---|---|
string | title: "Hello" or title: Hello | title, description, icon, ogImage |
number | order: 1 | order |
boolean | hidden: true | Optional (e.g. hide from sidebar) |
string[] | tags: [a, b] or tags: ["a", "b"] | tags |
You can use other YAML types (e.g. nested objects) for custom keys; only the fields in the table above are used by the framework for sidebar, SEO, JSON-LD structured data, OG, search, and machine-readable markdown.
How frontmatter is passed
- Next.js — The MDX pipeline uses
remark-frontmatterandremark-mdx-frontmatterwithname: "metadata". The parsed frontmatter is exposed as themetadataexport from the page. The layout andgenerateMetadatareceive it when resolving the page (e.g. viagetPage(params)), and it is used forcreatePageMetadata, sidebar tree (title, icon, order), description maps, and JSON-LD structured data. - TanStack Start / SvelteKit / Astro / Nuxt — The docs loader (e.g.
import.meta.globor content layer) reads each.md/.mdxfile, parses frontmatter, and builds the nav tree and page data. Frontmatter is passed to the layout as page data (e.g.page.dataor the props your content component receives). The same fields (title,description,icon,order,tags,ogImage) are used for sidebar, meta tags, JSON-LD structured data, and search.
So in all frameworks, the same frontmatter fields drive titles, descriptions, icons, ordering, JSON-LD, and OG; only the mechanism (MDX metadata vs. content loader page.data) differs. The related field is intentionally machine-readable first: add URL strings, and .md routes plus MCP page output render them next to Description as Related: /docs/a, /docs/b without adding a visible related-links block to the human page UI. A sibling agent.md remains a full override, so add a Related: line there manually if the override should include it.
createTheme(options)
Create a custom theme from scratch. See Creating Themes.
import { createTheme } from "@farming-labs/docs";
export const myTheme = createTheme({
name: "my-theme",
ui: {
colors: { primary: "#ff4d8d", background: "#0a0a0a" },
radius: "0px",
sidebar: { style: "bordered" },
},
});extendTheme(base, overrides)
Extend an existing theme with overrides.
import { extendTheme } from "@farming-labs/docs";
import { fumadocs } from "@farming-labs/theme";
export const myTheme = extendTheme(fumadocs(), {
ui: {
colors: { primary: "oklch(0.72 0.19 149)" },
},
});TanStack Start Server API
createDocsServer(config)
Creates the TanStack Start docs server adapter.
import { createDocsServer } from "@farming-labs/tanstack-start/server";Parameters:
| Property | Type | Description |
|---|---|---|
config | DocsConfig | The config object from defineDocs() |
rootDir | string | Optional project root used to resolve contentDir. Defaults to process.cwd() |
Returns:
| Property | Type | Description |
|---|---|---|
load | ({ pathname, locale? }) => Promise<DocsServerLoadResult> | Loads docs page data and the sidebar tree for a pathname |
GET | ({ request }) => Response | Search / markdown / llms.txt / OpenAPI / sitemap / AGENTS.md / skill.md endpoint handler |
POST | ({ request }) => Promise<Response> | AI chat endpoint handler |
import { createDocsServer } from "@farming-labs/tanstack-start/server";
import docsConfig from "../../docs.config";
export const docsServer = createDocsServer({
...docsConfig,
rootDir: process.cwd(),
});TanStack Start Components
| Component | Import | Description |
|---|---|---|
TanstackDocsPage | @farming-labs/tanstack-start/react | Built-in page renderer that resolves the compiled MDX module and wraps it in the shared docs layout |
RootProvider | @farming-labs/theme/tanstack | Root provider for theme state, search, and AI UI |
import { createFileRoute } from "@tanstack/react-router";
import { TanstackDocsPage } from "@farming-labs/tanstack-start/react";
import { loadDocPage } from "@/lib/docs.functions";
import docsConfig from "../../docs.config";
export const Route = createFileRoute("/docs/")({
loader: () => loadDocPage({ data: { pathname: "/docs" } }),
component: DocsIndexPage,
});
function DocsIndexPage() {
const data = Route.useLoaderData();
return <TanstackDocsPage config={docsConfig} data={data} />;
}import { createFileRoute } from "@tanstack/react-router";
import { docsServer } from "@/lib/docs.server";
export const Route = createFileRoute("/api/docs")({
server: {
handlers: {
GET: async ({ request }) => docsServer.GET({ request }),
POST: async ({ request }) => docsServer.POST({ request }),
},
},
});TanStack Start also uses the
docsMdx()Vite plugin from@farming-labs/tanstack-start/viteso.mdxfiles compile without extra local module-loader glue.
SvelteKit Server API
createDocsServer(config)
Creates all server-side functions for a SvelteKit docs site.
import { createDocsServer } from "@farming-labs/svelte/server";Parameters:
| Property | Type | Description |
|---|---|---|
config | DocsConfig | The config object from defineDocs() |
_preloadedContent | Record<string, string> | Pre-loaded markdown files from import.meta.glob. Required for serverless. |
Returns:
| Property | Type | Description |
|---|---|---|
load | (event) => Promise<PageData> | Layout load function (use in +layout.server.js) |
GET | (event) => Response | Search endpoint handler |
POST | (event) => Promise<Response> | AI chat endpoint handler |
import { createDocsServer } from "@farming-labs/svelte/server";
import config from "./docs.config";
const contentFiles = import.meta.glob(
["/docs/**/*.{md,mdx,svx}", "/AGENTS.md", "/AGENT.md", "/skill.md", "/.farming-labs/sitemap-manifest.json"],
{
query: "?raw",
import: "default",
eager: true,
},
) as Record<string, string>;
export const { load, GET, POST } = createDocsServer({
...config,
_preloadedContent: contentFiles,
});SvelteKit Components
Imported from @farming-labs/svelte-theme:
| Component | Props | Description |
|---|---|---|
DocsLayout | tree, config, title?, titleUrl? | Main layout with sidebar, nav, search, AI |
DocsContent | data, config | Page content with TOC, breadcrumb, footer nav |
DocsPage | entry, tocEnabled, breadcrumbEnabled, previousPage, nextPage, editOnGithub, lastModified | Low-level page wrapper (used by DocsContent) |
Astro Server API
createDocsServer(config)
Same as SvelteKit — returns { load, GET, POST }.
import { createDocsServer } from "@farming-labs/astro/server";Parameters:
| Property | Type | Description |
|---|---|---|
config | DocsConfig | The config object from defineDocs() |
_preloadedContent | Record<string, string> | Pre-loaded markdown files from import.meta.glob. Required for serverless. |
Returns:
| Property | Type | Description |
|---|---|---|
load | (pathname: string) => Promise<PageData> | Takes a URL pathname string, returns page data |
GET | ({ request }) => Response | Search endpoint handler for GET /api/docs?query=... |
POST | ({ request }) => Promise<Response> | AI chat endpoint handler with SSE streaming |
import { createDocsServer } from "@farming-labs/astro/server";
import config from "./docs.config";
const contentFiles = import.meta.glob(
["/docs/**/*.{md,mdx}", "/AGENTS.md", "/AGENT.md", "/skill.md", "/.farming-labs/sitemap-manifest.json"],
{
query: "?raw",
import: "default",
eager: true,
},
) as Record<string, string>;
export const { load, GET, POST } = createDocsServer({
...config,
_preloadedContent: contentFiles,
});Astro Components
| Component | Import | Description |
|---|---|---|
DocsLayout | @farming-labs/astro-theme/src/components/DocsLayout.astro | Main layout with sidebar, search, and theme toggle |
DocsContent | @farming-labs/astro-theme/src/components/DocsContent.astro | Page content with metadata |
DocsPage | @farming-labs/astro-theme/src/components/DocsPage.astro | Page structure (TOC, breadcrumb, nav) |
ThemeToggle | @farming-labs/astro-theme/src/components/ThemeToggle.astro | Light/dark toggle |
SearchDialog | @farming-labs/astro-theme/src/components/SearchDialog.astro | Search + AI dialog |
CSS Imports
Same pattern as SvelteKit:
| Import | Description |
|---|---|
@farming-labs/astro-theme/css | Default (fumadocs) theme |
@farming-labs/astro-theme/pixel-border/css | Pixel border theme |
@farming-labs/astro-theme/darksharp/css | Darksharp theme |
Theme Factories
| Factory | Import |
|---|---|
fumadocs() | @farming-labs/astro-theme |
pixelBorder() | @farming-labs/astro-theme/pixel-border |
darksharp() | @farming-labs/astro-theme/darksharp |
Nuxt Server API
defineDocsHandler(config, useStorage)
Creates a single Nitro event handler that serves docs loading, search, and AI chat for Nuxt.
import { defineDocsHandler } from "@farming-labs/nuxt/server";Parameters:
| Property | Type | Description |
|---|---|---|
config | DocsConfig | The config object from defineDocs() |
useStorage | Function | Nitro's useStorage utility for accessing server assets |
Returns:
A Nitro event handler that responds to GET (page loading + search) and POST (AI chat) requests.
import { defineDocsHandler } from "@farming-labs/nuxt/server";
import config from "../../docs.config";
export default defineDocsHandler(config, useStorage);Nuxt uses Nitro's
serverAssetsto load markdown files. Configurenitro.serverAssetsinnuxt.config.tsto point to your docs directory.
Nuxt Components
Imported from @farming-labs/nuxt-theme:
| Component | Props | Description |
|---|---|---|
DocsLayout | tree, config, triggerComponent? | Main layout with sidebar, nav, search, AI |
DocsContent | data, config | Page content with TOC, breadcrumb, footer nav |
CSS Imports
| Import | Description |
|---|---|
@farming-labs/nuxt-theme/fumadocs/css | Default (fumadocs) theme |
Theme Factories
| Factory | Import |
|---|---|
fumadocs() | @farming-labs/nuxt-theme/fumadocs |
Next.js API
RootProvider
Client-side provider that wraps your entire app. Enables search, theme switching (light/dark), and AI chat. Must be placed in your root app/layout.tsx.
import { RootProvider } from "@farming-labs/theme";
import "./global.css";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<RootProvider>{children}</RootProvider>
</body>
</html>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
search | SearchProviderProps | { options: { api: "/api/docs" } } | Search configuration. API defaults to the unified docs handler. |
theme | ThemeProviderProps | — | Theme provider options (passed to next-themes) |
children | ReactNode | required | Your app content |
suppressHydrationWarningon<html>prevents React warnings caused by the theme class being injected before hydration.
withDocs(nextConfig)
Wraps your Next.js config with docs framework support. Handles MDX, routing, search index generation, and auto-generates mdx-components.tsx and app/docs/layout.tsx if missing.
import { withDocs } from "@farming-labs/next/config";
export default withDocs({});createDocsLayout(config)
Creates the docs layout component from your config. Auto-generated by withDocs() — you typically don't need to create this manually.
import { createDocsLayout } from "@farming-labs/theme";
import config from "@/docs.config";
const { DocsLayout } = createDocsLayout(config);
export default function Layout({ children }) {
return <DocsLayout>{children}</DocsLayout>;
}How is this guide?