Configuration Reference
Stele works with zero configuration. Adding .stele.yml files to your repository lets you override defaults.
All fields are optional. If a field is omitted, its default applies.
Quick example
Root .stele.yml:
roots:
- docs/
- artifacts/
exclude:
- artifacts/scratch/
landing: docs/overview.md
file_browser:
enabled: true
path: /_browse
Per-directory docs/.stele.yml:
landing: getting-started.md
exclude:
- drafts/
Per-directory configuration
Stele supports .stele.yml at any level of your repository — not just the root. Each config file controls behaviour for its directory.
- Root
.stele.yml— site-wide settings:roots,exclude,landing,file_browser - Sub-directory
.stele.yml— directory-scoped settings:roots,exclude,landing
The file_browser setting only works in the root config. Per-directory configs that include it will have it ignored.
Precedence
When configs exist at multiple levels, parent always wins over child:
exclude— patterns are additive. A child directory can add more exclude patterns but cannot remove ones set by a parent.roots— narrowing-only. A child can restrict which of its subdirectories are indexed, but cannot widen what a parent already excluded. Arootslist means the same thing at every level: seeroots.landing— each config'slandingapplies only to its own directory. There is no conflict between levels.
Example: if root excludes *.draft.md, a sub-directory config cannot un-exclude draft files.
Path resolution
Paths in a .stele.yml are always relative to the directory the config lives in:
# In docs/.stele.yml:
landing: getting-started.md # resolves to docs/getting-started.md
exclude:
- scratch/ # excludes docs/scratch/
roots:
- guides/ # only indexes docs/guides/
Field reference
roots
Restrict which subdirectories Stele indexes. Only files within these directories (and their subdirectories) are included.
roots:
- docs/
- artifacts/
- Type: list of strings (directory paths, relative to config location)
- Default: entire directory
- Available in: root and per-directory configs
Only files beneath a listed directory are indexed, so files sitting directly in the config's own directory are left out — README.md for a root config, docs/intro.md for docs/.stele.yml. An entry can name a nested directory (guides/api/). An entry that is absolute or contains .. fails validation.
Use this when your repo contains code alongside documentation and you only want the documentation published.
exclude
Patterns to exclude from indexing, in addition to your .gitignore and Stele's built-in ignores.
exclude:
- artifacts/scratch/
- tmp/
- "**/*.draft.md"
- Type: list of strings (gitignore-style patterns, relative to config location)
- Default: none (only
.gitignoreand built-in ignores apply) - Available in: root and per-directory configs
Patterns follow .gitignore rules, anchored at the config's directory: in docs/.stele.yml, /scratch/ and guides/wip.md mean docs/scratch/ and docs/guides/wip.md, while an unanchored pattern such as drafts/ matches at any depth below docs/. As in .gitignore, a slash anywhere but at the end anchors a pattern, so _kit/bin/ matches only _kit/bin/ beside the config; write **/_kit/bin/ to match it at any depth.
Built-in ignores that always apply:
node_modules.git.DS_Store*.log- Hidden directories (names starting with
.), except.stele.ymlitself - Files larger than 25 MB
landing
The file that serves as the landing page for this directory.
landing: overview.md
- Type: string (file path relative to the config's directory)
- Default: resolved automatically (see landing page resolution)
- Available in: root and per-directory configs
In a root config, this sets the site home page. In a sub-directory config, this sets what visitors see when navigating to that directory.
access
Narrow who may open the pages this directory governs, to one of the four access values:
# In clients/acme/.stele.yml:
access: restricted
- Type:
restricted,internal,invitedorlink - Default: none; the site's access setting alone decides
- Available in: root and per-directory configs
The value names a set of people, the same sets as the site's Access setting in the portal: restricted is only the people on the site's viewer list (Axelerant colleagues included), internal is everyone at Axelerant, invited is everyone at Axelerant plus the viewer list, and link is all of those plus anyone holding the share link. A page is open to someone only if the site's setting and every folder value above the page admit them; see how settings combine. So a folder can only narrow, never widen: access: link inside a folder marked restricted is still restricted, and nothing in a repository can open a site wider than the portal set it.
The folder's value applies to everything under it: pages, assets, compiled components, the file browser, and the assistant, which never answers from pages the asker cannot open. A request for a path under the folder is refused whether or not the page exists, so a guessed path reveals nothing. Someone the folder does not admit is sent to sign in, or, when they are signed in with Google already, told they have no access. Folders a viewer may not open are left out of the file browser as a courtesy; the refusal is the guarantee.
Typical uses: a client folder in a shared presales repository (access: restricted, with the client's addresses and the account team on the site's viewer list), or an internal folder on a site shared outside Axelerant (access: internal).
file_browser
Configure the built-in file explorer, which provides a browsable directory listing of your site's content.
file_browser:
enabled: true
path: /_browse
Sub-fields:
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Set to false to disable the file browser entirely |
path |
string | /_browse |
URL path for the file browser. Must start with /_ |
- Type: object (optional)
- Default: enabled at
/_browse - Available in: root config only
build
Run a command in this directory at sync time and serve its output as the directory's content. Useful for diagram tools and generators that turn source files into HTML/SVG.
build:
command: likec4 build . --output-single-file
output: dist/
Sub-fields:
| Field | Type | Required | Description |
|---|---|---|---|
command |
string | yes | Shell command, executed inside the directory containing the .stele.yml. |
output |
string | yes | Subdirectory whose contents replace this directory in the indexed tree. A trailing slash is optional. |
- Type: object (optional)
- Default: none — no build runs
- Available in: root and per-directory configs
- Inheritance:
builddoes not inherit. Each directory declares its own; a parent'sbuilddoes not run for child directories.
How it behaves:
- The worker
cds into the manifest's directory and runscommand. - Only the contents of
output/are then indexed for that directory, with paths rebased up. A file atarchitecture/dist/index.htmlis served atarchitecture/index.html. - Source files (everything outside
output/) are not served as pages. They remain in Git. excludestill applies to the output, matched against the path the build wrote on disk. To hide a generated page, writeexclude: [dist/404.html], not the rebased404.htmlyou see in the file browser..gitignoreand the built-in ignores do not apply to the output, so gitignoringdist/is safe.- Each build has a 2-minute timeout. The build runs with a fresh
/tmpHOME and a fresh npm cache on every sync — there is no persistent state. - A non-zero exit code fails the entire sync for that site. The error appears in the portal.
Available tools in the build environment
The Stele worker container ships a fixed, small set of tools. Build commands can only invoke what's pre-installed — anything else fails with "command not found".
| Tool | Purpose |
|---|---|
node, npm, npx |
Node 24 + full npm registry access |
git |
Repo / history introspection |
dot (graphviz) |
.dot / .gv rendering, used as a backing renderer by some tools |
likec4 |
LikeC4 CLI, pre-installed globally |
d2 |
Terrastruct's D2 binary, pre-installed |
curl |
Generic HTTP fetches in build scripts |
npx-based tools that are pure-npm (e.g. esbuild, vite, marked) work, but pay a download cost on every sync because the npm cache is not persisted between syncs.
If you need a tool that isn't on this list (PlantUML, mermaid-cli, Structurizr CLI, language toolchains beyond Node, etc.), open an issue — we'll evaluate adding stable, lightweight tools to the image.
Examples
LikeC4 — author .c4 / .likec4 sources, render to a single self-contained HTML file:
# architecture/c4/.stele.yml
exclude:
- dist/404.html
build:
command: likec4 build . --output-single-file
output: dist/
D2 — author .d2 sources, render an SVG and wrap it in an HTML landing page:
# infra/d2/.stele.yml
build:
command: |
mkdir -p dist
d2 architecture.d2 dist/architecture.svg
printf '<!doctype html><meta charset=utf-8><title>Architecture</title><style>body{margin:0;padding:24px;font-family:system-ui}svg{max-width:100%;height:auto}</style>' > dist/index.html
cat dist/architecture.svg >> dist/index.html
output: dist/
Graphviz — render every .dot file in the folder to SVG:
# diagrams/.stele.yml
build:
command: |
mkdir -p dist
for f in *.dot; do
dot -Tsvg "$f" -o "dist/${f%.dot}.svg"
done
output: dist/
In every case, gitignore the output/ directory — Stele rebuilds it on every sync.
ask
Declare a corpus: the documents an assistant may read to answer readers' questions from this directory down. Who may ask is audience, a set of people named like access above and intersected with it: the assistant never answers from pages the asker cannot open (see Who may ask).
ask:
name: Platform Docs
audience: internal
- Type: object (optional);
nameis the only required sub-field - Default: none — no assistant
- Available in: root and per-directory configs
- Inheritance: each block is its own corpus, rooted at its directory; the nearest block wins for a page.
promptconcatenates ancestor-first;audience,sourcesandexcludecome from the nearest block only. - Limits:
nameat most 80 characters;promptat most 2,000 per block and 4,000 once joined with its ancestors'. Longer text is cut short with a warning on the site's Ask card, not refused (details).
The block cannot name a provider or model; a site owner sets those in the portal, where Ask is also switched on. The fields, what the assistant can read, and where citations point are in the Ask guide.
collections
Say that this directory is a collection of one of Stele's built-in content types, so that Stele lists its items as a generated table on the directory's landing. Each item is one Markdown file. What the list shows, the frontmatter fields each type reads, relations between items and the runbook alerts file are in Collections; this section is the setting itself.
# In decisions/.stele.yml:
collections: adr
- Type:
none, a type name, or a block withkind,appendandstatus_field - Types:
adr(architecture decision records),runbook, andfinding(audit findings from a technical audit, one per file) - Default: none, except at the conventional locations
docs/adr/,docs/runbooks/anddocs/audits/, which are collections of that type with no configuration - Available in: root and per-directory configs
- Inheritance: a setting covers its directory and every subdirectory, unless a subdirectory's own
.stele.ymlsetscollectionstoo. The nearest setting wins outright; nothing is merged key by key. A directory holds one type.
It takes three forms:
# A collection of one type, with every default
collections: adr
# The same, changing only what differs
collections:
kind: adr # required in a block: adr, runbook or finding
append: false # don't add the view below this directory's README
status_field: state # read each item's status from `state:` instead of `status:`
# Not a collection, even at a conventional location
collections: none
kindnames the type. It is required in the block form.appenddecides whether the view is added below a Markdown landing such asREADME.md(defaulttrue). Set it tofalsewhen the README already lists its items by hand. An HTML, TSX or JSX landing is never changed either way.status_fieldis the frontmatter field items keep their status in, when it is not the type's own.
none turns a collection off for the directory and its subdirectories, which can turn it back on with a setting of their own. Write the word none: an empty value, ~, null and false are mistakes, not ways of saying none.
A mistake inside collections: does not fail the directory the way other mistakes do. The pages are published as usual, the collection is skipped, and the portal lists the problem with its line on the site's overview. See Validation.
theme
Choose how Stele draws the pages of this directory and everything below it. The themes, what each looks like, and how an HTML page opts in are in Themes.
# In docs/codebase-guide/.stele.yml:
theme: guide
- Type:
defaultorguide - Default:
default - Available in: root and per-directory configs
- Inheritance: the nearest setting wins. A Markdown page's frontmatter
theme:overrides it for that page.
nav
Curate the sidebar of a theme's navigation, which starts at the directory that names a theme or declares nav. Without it, the sidebar is derived from folders, frontmatter weight and titles. With it, the groups listed come first, and every page they leave out still appears after them. The format is in Curating the order with nav:.
nav:
- group: Orientation
pages:
- path: index.md
title: Overview
- wp-repository.md
- ../findings/
- Type: a list of page paths,
{ path, title }blocks, and{ group, pages }blocks - Default: none: the derived sidebar
- Available in: root and per-directory configs
- Paths: relative to this directory; a trailing
/means a folder's landing
A mistake inside theme or nav does not fail the directory, as with collections. See Validation.
Complete schema
For reference, here is every field and its type:
# Root .stele.yml — all fields optional
roots: # list of directory paths
- string
exclude: # list of glob patterns
- string
landing: string # file path for this directory's landing
file_browser: # file browser configuration (root only)
enabled: boolean # default: true
path: string # default: /_browse, must start with /_
build: # optional build step for this directory
command: string # shell command run inside this directory
output: string # subdirectory whose contents replace this directory's content (no '..')
ask: # optional assistant over this directory's documents (see ask.md)
name: string # required if ask: is present; max 80 characters
audience: string # internal (default) | invited | all
prompt: string # optional guidance; max 2,000 characters (4,000 with ancestors)
sources: # optional extra glob patterns to make readable
- string
exclude: # optional paths the assistant must not read
- string
renders: # optional: data file -> page that renders it
path/to/file.json: string | { page: string, anchor_field: string }
collections: string # none | adr | runbook | finding
# or, as a block:
collections:
kind: string # required in a block: adr | runbook | finding
append: boolean # default: true
status_field: string # default: the type's own status field
theme: string # default | guide (see themes.md)
nav: # optional curated sidebar (see themes.md)
- string # a page path, relative to this directory
- path: string
title: string
- group: string
pages:
- string
# Per-directory .stele.yml — all fields optional
roots: # restrict indexing to these subdirectories
- string
exclude: # additional exclude patterns for this directory
- string
landing: string # file path for this directory's landing
build: # optional build step for this directory
command: string
output: string
ask: # a corpus rooted at THIS directory (see ask.md)
name: string
collections: string # or a block, as above; covers THIS directory and below
theme: string # covers THIS directory and below
nav: # starts a sidebar rooted at THIS directory
- string
Validation
Stele checks every .stele.yml when it syncs. A file that is not valid is not used, and nothing under its directory is published until it is fixed. For the root .stele.yml, that means the whole site. Stele never falls back to defaults for a broken file, because the defaults publish more than you meant to: a typo in exclude would otherwise put every excluded file on the site.
A file is invalid when:
- It is not valid YAML: bad indentation, an unclosed
[or quote, or the same key written twice. - It has a setting Stele does not know. A misspelt key such as
exlude:is an error, not something quietly ignored. Stele suggests the setting it thinks you meant. - A value is the wrong kind, such as a
file_browser.paththat does not start with/_, anask.audienceother thaninternal,invitedorall, or arootsorbuild.outputentry that is absolute or contains... - A required field is missing:
buildneeds bothcommandandoutput, andaskneedsname.
An empty file, or one with only comments, is valid and sets nothing.
collections is the exception. A mistake inside it, such as an unknown type, an unknown key in the block, or a value of the wrong kind, does not make the file invalid. The rest of the file is used, the pages are published as usual, and only the collection is skipped: the directory is not a collection until the mistake is fixed. A mistake there can only leave a generated list out, never publish anything, so it is not worth taking pages down for. A misspelt collections key itself, such as colections:, is still an unknown setting and fails the file. theme and nav work the same way: an unknown theme is ignored and the directory keeps the theme it would otherwise have, and a nav list with any mistake in it is ignored whole, leaving the sidebar Stele derives.
What you see when a file is invalid:
- In the portal, the site's overview lists each invalid file with its problems and their line numbers, and the site shows invalid config in the sites list. The sync itself still succeeds, and the rest of the site is published as usual.
- On the site, a page under the invalid file's directory shows an explanation instead of the page. Signed-in Axelerant staff see which file is wrong and why. Anyone who opened the site through a share link or an invite sees only that the page is unavailable.
- In Ask, nothing under that directory is read. A corpus that contains it shows a warning in the site's Ask settings.
- Through MCP,
resolve_linksreturnsMANIFEST_INVALIDfor those paths, with the file and its problems.
Fix the file and push it. The sync that follows publishes the directory again.
A valid file can still have a setting that does nothing. When a roots entry names a folder that is not there, or an exclude pattern matches no file, the sync succeeds and the site is published as if the line were not there, and the portal lists it on the site's overview under Configuration that has no effect. This is usually a typo or a renamed folder. A mistake inside collections:, theme: or nav: is listed there too, with its line. For a built site, remember that exclude is matched against the path on disk (dist/404.html), not the rebased path the file browser shows. An ask.exclude pattern that matches nothing is judged the same way, but it is a property of the corpus, so it is listed on the site's Ask card instead; see Ask.
ask.provider and ask.model are accepted and ignored. They were retired when the provider and model moved to the portal, and a repository that still has them keeps working.