Docs as code

Project layout

A docs project is a folder of Markdown, one .askvoro file, and a section.json wherever you want a section.

text
docs/
├── .askvoro          identity — which workspace and knowledge base. init writes it.
├── site.json         header and footer links. Optional.
├── 01-welcome.md
├── getting-started/
│   ├── section.json
│   ├── 01-install.md      →  /~/<workspace>/<knowledge-base>/install
│   └── 02-first-steps.md  →  /~/<workspace>/<knowledge-base>/first-steps
├── billing/
│   ├── section.json
│   └── invoices.md
└── faqs/
    ├── 01-do-you-ship-internationally.md
    └── 02-how-do-i-cancel.md

Two files carry configuration, and they are separate on purpose. .askvoro is identity — which workspace, which knowledge base, which source — generated once by askvoro init and rarely touched. site.json is presentation, and you edit it whenever your marketing site grows a page. See Site navigation.

Titles come from the first heading#

The first # heading in a file becomes the article title and is removed from the body — so it isn't rendered twice.

markdown
# How to cancel your subscription

Open Account → Subscription and choose Cancel.

That becomes an article titled "How to cancel your subscription" whose body starts at "Open Account".

Without an # heading, the filename becomes the title. Rely on the heading; it reads better and survives a rename.

Number your files to order them#

Files sort lexicographically, and that becomes article order. Numeric prefixes give you explicit control:

text
01-install.md
02-first-steps.md
03-troubleshooting.md

The prefix is only for sorting. It never appears in the title, and it never appears in the URL01-install.md publishes at /install. Renumbering a file to reorder it does not move its page.

Use 01, not 1. Ten files sorted as strings put 10- between 1- and 2-.

Prefixes are optional: an unprefixed file sorts alphabetically among its siblings, so a folder can mix both.

Choosing a URL#

The URL segment comes from the filename with the ordering prefix stripped. When you want something different, set slug: in frontmatter:

markdown
---
slug: install
---

# Install the widget on your site

That publishes at /install instead of /install-the-widget-on-your-site.

Unlike the derived slug — which is computed once, when the article is first created, so a published page never moves on its own — slug: is applied on every push. It is how you deliberately rename a live page.

You need it in one other case: a four-digit prefix is left alone so 2026-pricing-change.md keeps its year, but three digits are treated as an ordinal, so 404-page.md would publish at /page. Set slug: 404-page to keep it.

If two files would land on the same URL — getting-started/01-overview.md and billing/02-overview.md both want /overview — the push fails and names both files. Give one a slug:.

Sections#

Each directory can hold a section.json:

json
{
  "title": "Getting started",
  "position": 10
}

Markdown inherits the nearest section.json above it. A file with none is pushed as uncategorized.

Leave gaps in position — 10, 20, 30 — so you can insert a section later without renumbering the rest.

FAQs#

With a faqs block in .askvoro, Markdown under that directory becomes FAQs instead of articles: the first # heading is the question, the rest is the answer.

markdown
# Do you ship internationally?

Yes, to most countries. Shipping is calculated at checkout.

Filename order becomes FAQ order inside this knowledge base, written in the same transaction as the articles. The dashboard edits those same rows.

Conventions worth adopting#

One question per file, matching the article advice in the help center guide.

Filenames that match the title. 02-how-do-i-cancel.md for "How do I cancel?" makes the folder browsable without opening anything.

Reordering is safe. Files are matched to existing articles by path, but a file that moved is recognised by the URL it resolves to, so renumbering 01-install.md to 03-install.md updates the article in place — same page, same link. Renaming the part of the filename that becomes the URL is a different article; see Pushing.