productdoco.yaml
One file at the root of your repository. It is what makes the repository a project: name and docs say what to call it and where its documentation lives. Below those sit two independent blocks — nav, which orders, titles and groups the documentation menu, and services, which fills the services board with what the product runs on. Either of those two may be left out.
ProductDoco only ever reads it. Nothing in the app writes to your repository, so this file is the whole way to configure either surface — and it is versioned with your code, read at whatever branch, tag or commit you are looking at.
The two blocks fall back totally: an unreadable nav orservices renders the menu the folder would have produced on its own and a board with no services, and says what it dropped. docs is the exception — it is what confines every read to your documentation folder, so a missing or unusable value is refused rather than guessed at.
Where the file goes
At the root of the repository, not inside the docs folder. It has to sit above that folder because it is the thing that names it: a file findable only inside the folder it defines could never be found at all.
your-repo/├── productdoco.yaml ← this file├── src/└── docs/ ← whatever `docs:` names ├── overview.md ├── integrations.md └── features/ ├── services.md └── markdown-rendering.mdA repository your ProductDoco installation can reach that carries this file is a project — there is nothing to create in the app, and nothing about it is stored there. Rename it, or move its documentation, by editing this file.
productdoco.yml works too. If both spellings exist the .yaml one is used and the other is reported as ignored.
Quick start
version is required and is always 1.docs is required for the repository to be readable at all — it is the folder every path below is resolved against, and the boundary every read is confined to. name is optional and falls back to the repository’s own name; so are nav and services.
# productdoco.yaml, at the root of your repositoryversion: 1 # What this project is called, and where its documentation lives.name: Acme Platformdocs: docs # The documentation menu, in the order you want it.nav: - overview.md - path: integrations.md title: Integrations & webhooks - title: Features items: - features/services.md - path: features/markdown-rendering.md title: Markdown # The services board.services: - Plausible - name: Vercel icon: vercel account: team_a1b2c3 cost: amount: 20 currency: USD per: month links: - title: Dashboard href: https://vercel.com/productdoconav: the docs menu
Without this block the sidebar is built from whatever markdown is in the folder: folders first, then files, each alphabetical, labelled by filename. With it, the menu is the order you write, under the labels you write, grouped however you like.
An entry is either a page or a group, and never both. A page can be written two ways; a group is a title and its items, which hold entries of either shape, up to five levels deep.
nav: # A bare path. The label comes from the filename. - overview.md # A path with a label of your own. - path: integrations.md title: Integrations & webhooks # A group. It needs a title, and holds entries of either shape. - title: Features collapsed: true items: - features/services.md - title: Rendering items: - features/markdown-rendering.mdKeys
| Key | Where | Meaning |
|---|---|---|
path | entry | A markdown file, relative to the docs folder. A bare string is shorthand for it. |
title | entry | The sidebar label. Required on a group, optional on a page. |
items | entry | Makes the entry a group. A group cannot also have a path. |
collapsed | group | true renders the group closed. Everything else opens expanded. |
Rules
- Paths are relative to the docs folder — not the repository root. With docs in
docs/, writefeatures/services.md, neverdocs/features/services.md. Forward slashes only;..and absolute paths are refused, not resolved. - Files you do not list still appear — They are appended after everything nav places, keeping their folder structure. The block re-orders and re-labels the menu; it never hides a page.
- A folder emptied by the config disappears — Pull every page out of
features/into groups and thefeaturesfolder stops rendering; pull out some and it renders with the rest. - Anything listed twice appears once — At the first place it was listed. Later mentions are reported and dropped.
- Groups are not folders — A group named Features and a real
features/folder can coexist, and a group can collect pages from anywhere in the tree. Only markdown files are ever selectable — a group is a heading, not a page. - Only markdown is allowed —
.mdand.mdx, inside the docs folder. - A bad nav is dropped whole — A block that cannot be trusted falls back to the folder contents, because a half-applied menu is harder to debug than none. A single bad entry only costs that entry.
services: the board
The services a product runs on — with the links you actually open, the account each is billed under, and what it costs. ProductDoco renders this list; it never writes it. Leave the block out and the board is simply empty.
services: # A bare string: a service with nothing but a name. - Plausible # Everything a service can carry. - name: Vercel icon: vercel account: team_a1b2c3 cost: amount: 20 currency: USD per: month links: - title: Dashboard href: https://vercel.com/productdoco - title: Logs href: https://vercel.com/productdoco/logsKeys
| Key | Meaning |
|---|---|
name | Required, written as you would write it. A bare string entry — - Plausible — is shorthand for a service with nothing else. |
icon | A Simple Icons slug. Optional; without it the name is matched against the 87 built-in services, and anything unmatched shows its initials. |
account | The account the service is billed under. The board can group by it. |
cost | A mapping: amount (a number, required), currency (optional, free text) and per — month or year, defaulting to month. Omit the key and the service shows no cost. |
links | A list of title and href pairs. Only http and https addresses are shown; a link with no title falls back to its address. |
Rules
- The board follows the ref you are reading — Switch branch or tag in the document bar and the services switch with it, because they come from the file at that commit.
- Order is yours — Services render in the order the file lists them.
- Names are matched loosely for icons —
Vercelfinds the built-invercelicon;Vercel (staging)does not, and takes an expliciticon:if you want one. Writingicon:always wins. - Two services may share a name — They stay separate entries on the board.
- A yearly cost is divided — when the board totals monthly spend.
- Links are the one real trust boundary — and are treated as one: anything that is not an
http(s)address is dropped rather than rendered.
When something is wrong
Nothing here fails loudly. A problem costs the smallest thing it can, and the surface it affects says what it dropped in a warning box above itself. The two blocks fail independently: a broken services list never costs you the menu.
In the menu
| Warning | Cause |
|---|---|
| could not be read as YAML | The file does not parse. Anchors and aliases are refused on purpose. |
needs version: 1 | The version key is missing, or is a version this release does not know. |
| must be a mapping with a version and a nav | The file's top level is a list or a plain value. |
| is outside the docs folder | The path escapes the folder, or is absolute. |
| is not a markdown file | The path is not .md or .mdx. |
| was not found at this version | No such file at the branch, tag or commit being viewed. |
| is listed more than once | Only the first mention placed the page. |
| a group needs a title | A group entry has items but no title. |
| has both a path and items | An entry tries to be a page and a group at once. |
| the group has nothing left to show | Every item in a group was refused, so the group went with them. |
| folders more than 5 levels · more than 1000 entries · stopped after 100 folders | The docs folder itself was too big to crawl whole. These share the same box and are listed first, because they explain any “was not found” below them. |
On the board
| Problem | What happens |
|---|---|
Not YAML, not a mapping, or missing version: 1 | No services, one warning |
| The file is larger than 64 KB | Not read, one warning |
services: | Not a list — no services, one warning |
| An entry is not a string or a mapping, or has no name | That entry is skipped |
| More than 200 services | The rest are skipped |
cost is not a mapping, or cost.amount is not a number | That service renders without a cost |
cost.per is neither month nor year | Read as month |
| A link has no href, or an href that is not http(s) | That link is dropped |
| More than 20 links on one service | The rest are dropped |
No services: key at all is not a problem, and says nothing. Neither is no nav:.
Limits
A config is a hand-written file, not a data dump, so the numbers are deliberately small.
| Limit | Value |
|---|---|
| File size | 64 KB |
nav entries | 500, nested at most 5 levels |
services entries | 200, with at most 20 links each |
| Warnings shown | 10, then a count of the rest |
The file is content from a repository like any other, so it is parsed with the YAML core schema only and with alias resolution disabled — no custom tags, and no alias expansion. Every path nav names is checked against the docs folder and then matched to a file the crawl already found; nothing read out of the config is ever used to ask GitHub for anything.
Give this to an agent
Point a coding agent at this page, or paste the spec below into its prompt. It is the whole format in one block — both schemas, the rules and the limits — with nothing on the page it needs to have read first.
Write productdoco.yaml at the ROOT of this repository, not inside the docsfolder — it names that folder, so it has to sit above it. productdoco.yml isaccepted too; if both exist, .yaml wins. A repository carrying this file is a ProductDoco project. Nothing is stored inthe app: the file is the whole configuration. version: 1 # required, must be exactly 1 name: <label> # optional, defaults to the repository name docs: <folder> # required: the documentation folder, e.g. docs branch: <branch> # optional, defaults to the repository default branch nav: [ ... ] # optional: the documentation menu services: [ ... ] # optional: the services board docs rules:- A folder inside the repository, written without leading or trailing slashes.- "/", "..", absolute paths, backslashes and empty segments are refused.- Every read is confined to it; an unusable value means the project cannot be read at all, so this is the one key that is never guessed at. nav — the documentation sidebar, in the order written: - <path> # shorthand for { path: <path> } - path: <path> # a markdown file, relative to the docs folder title: <label> # optional sidebar label - title: <label> # a group: title is required collapsed: false # optional, true renders the group closed items: [ ... ] # required, entries of either shape, max 5 deep nav rules:- Paths are relative to the docs folder, never the repo root: with docs in docs/, write features/canvas.md, not docs/features/canvas.md.- Forward slashes only. ".." and absolute paths are refused, not resolved.- Only .md and .mdx files can be listed.- An entry has either a path or items, never both.- Files left out of nav still appear, appended after everything nav places, keeping their folder structure. nav re-orders; it never hides a page.- A path listed twice is used at its first mention only.- A bad nav is dropped whole and the menu falls back to the folder contents. services — the board, in the order written: - <name> # shorthand for { name: <name> } - name: <name> # required; written as a person would write it icon: <slug> # optional Simple Icons slug account: <id> # optional; the board can group by it cost: # optional amount: <number> # required inside cost currency: <code> # optional, free text, e.g. USD per: month | year # optional, defaults to month links: # optional, max 20 - title: <label> # optional, falls back to the href href: <url> # required, http(s) only services rules:- Without an icon, the name is matched against the 87 built-in services; anything unmatched shows its initials. An explicit icon always wins.- Two services may share a name; they stay separate entries.- A yearly cost is divided when the board totals monthly spend.- A bad service entry is dropped on its own; the rest of the board renders. Both blocks: no YAML anchors or aliases (alias resolution is disabled, and afile using them is refused whole), 64 KB maximum, and the file is read at thebranch, tag or commit being viewed.Limits: nav 500 entries / 5 levels deep; services 200 entries / 20 links each.Once the file is committed, open the project and it is picked up on the next read — no setting to flip, and nothing to do in ProductDoco itself.