GuidesConfiguration Reference

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. A roots list means the same thing at every level: see roots.
  • landing — each config's landing applies 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 .gitignore and 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.yml itself
  • 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, invited or link
  • 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: build does not inherit. Each directory declares its own; a parent's build does not run for child directories.

How it behaves:

  • The worker cds into the manifest's directory and runs command.
  • Only the contents of output/ are then indexed for that directory, with paths rebased up. A file at architecture/dist/index.html is served at architecture/index.html.
  • Source files (everything outside output/) are not served as pages. They remain in Git.
  • exclude still applies to the output, matched against the path the build wrote on disk. To hide a generated page, write exclude: [dist/404.html], not the rebased 404.html you see in the file browser. .gitignore and the built-in ignores do not apply to the output, so gitignoring dist/ is safe.
  • Each build has a 2-minute timeout. The build runs with a fresh /tmp HOME 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); name is 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. prompt concatenates ancestor-first; audience, sources and exclude come from the nearest block only.
  • Limits: name at most 80 characters; prompt at 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 with kind, append and status_field
  • Types: adr (architecture decision records), runbook, and finding (audit findings from a technical audit, one per file)
  • Default: none, except at the conventional locations docs/adr/, docs/runbooks/ and docs/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.yml sets collections too. 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
  • kind names the type. It is required in the block form.
  • append decides whether the view is added below a Markdown landing such as README.md (default true). Set it to false when the README already lists its items by hand. An HTML, TSX or JSX landing is never changed either way.
  • status_field is 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: default or guide
  • 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.

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.path that does not start with /_, an ask.audience other than internal, invited or all, or a roots or build.output entry that is absolute or contains ...
  • A required field is missing: build needs both command and output, and ask needs name.

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_links returns MANIFEST_INVALID for 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.