# Fumadocs Page Tree

> Structure a Fumadocs page tree — content folders, meta.json, slugs, sidebar. Use when adding, moving, or reordering doc pages under a content directory, editing a meta.json, creating sidebar sections (folder groups, clickable section headers), or controlling sidebar expansion.

- Skill: `aidenlx/fumadocs-page-tree` (Agent Skill)
- Install (CLI): `npx skillmds@latest add aidenlx/fumadocs-page-tree`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aidenlx/fumadocs-page-tree/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: aidenlx (https://skillmd.com/u/aidenlx)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/aidenlx/fumadocs-page-tree

---


# Fumadocs page tree

Page URLs and the sidebar both derive from the content directory layout plus per-folder `meta.json`. This file covers the load-bearing rules and the traps; verify anything beyond it against the local checkout `~/repo/fumadocs` — conventions in `apps/docs/content/docs/headless/page-conventions.mdx`, ground truth in `packages/core/src/source/plugins/slugs.ts` (slugs) and `packages/core/src/source/page-tree/builder.ts` (tree). In this repo `fumadocs-ui` is aliased to `@fumadocs/base-ui` (`packages/base-ui` in the checkout); the layout API is identical.

## Slugs

- `dir/page.mdx` → `/docs/dir/page`; `dir/index.mdx` → `/docs/dir`.
- A parenthesized folder `(name)/` is **slug-transparent**: stripped from slugs, so children keep parent-level URLs. Use it for sidebar-only grouping.
- Each URL may appear once in the entire tree — duplicates are an error by design.

## meta.json

Folder fields: `title`, `icon`, `description`, `defaultOpen`, `collapsible`, `root`, `pages`, `pagesIndex`.

`pages` entry syntax:

| Entry           | Meaning                                             |
| --------------- | --------------------------------------------------- |
| `"page"` / `"dir"` | path to a page or folder (relative paths allowed) |
| `"(group)"`     | folder group, referenced by its literal name        |
| `"---Label---"` | separator (icon form: `---[Icon]Label---`)          |
| `"[Text](url)"` | link; `external:[Text](url)` marks it external      |
| `"..."` / `"z...a"` | remaining items, alphabetical / reversed        |
| `"...dir"`      | inline a folder's items into this level             |
| `"!item"`       | exclude from `...`                                  |

- `pages` is **exhaustive**: once present, unlisted items are dropped unless `...` appears. Use an explicit array to pin sidebar order.
- `"root": true` makes a root folder: rendered as layout tabs, and only the active root's items are visible.

## Clickable section header (folder index)

A folder containing an `index` page attaches it as the folder's own link: the header becomes clickable and the index leaves the child list.

- Keep `pages` free of `"index"` — the index auto-resolves. Listing it demotes the folder to a plain label with the index as an ordinary child row (`builder.ts` deletes `node.index` when `pages` claims it).
- `pagesIndex` overrides which page (or `[Text](url)` link) serves as the folder's index.
- Slug-transparent group + index composes into "section header linking to a parent-level URL": `(intro)/index.mdx` gives a clickable **Intro** header → `/docs`, with siblings like `(intro)/install.mdx` → `/docs/install`.

## Sidebar expansion

- `<DocsLayout sidebar={{ defaultOpenLevel: n }}>` — a folder starts open when `defaultOpenLevel >= depth` (top-level folders are depth 1) or when it contains the active page. Default `0`: everything starts collapsed.
- Per-folder overrides in its `meta.json`: `defaultOpen: true` opens one folder; `collapsible: false` pins it open with no toggle.

