Configuration

This content was generated with the assistance of AI and should be verified against the official documentation of each tool before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

This page was written against Zensical 0.0.72 (the latest release on PyPI, published 2026-10-23, Alpha status) and checked on 2026-10-26. Zensical announced a 0.1.0 release line for 2026-11-05, so key names and limits below may change; see Roadmap and Upgrade Policy. All examples were run with that version against a small Pocket Units documentation tree.

What configures a Zensical project

A Zensical project is configured by one file at the project root, normally zensical.toml, which zensical new generates with commented examples (see Installation and CLI). Zensical can also read an existing mkdocs.yml, which makes migration gradual: the same project builds with either mkdocs build or zensical build (see Migrating from Material for MkDocs).

The two formats describe the same settings. The official configuration reference shows every setting twice, once for zensical.toml and once for mkdocs.yml, and recommends the mkdocs.yml form while a project must keep working with both builders. This page does the same.

Every setting of zensical.toml currently lives in a single table declared on the first line:

[project]

The documentation says that further tables may be introduced later and that settings may then move out of [project], with automatic refactorings promised so that no manual migration is needed. Nested settings use dotted tables such as [project.theme], [project.extra] or [project.plugins.mkdocstrings.handlers.python].

Which file wins

When a directory contains both zensical.toml and mkdocs.yml, a plain zensical build read zensical.toml in the test run: the page title came out as the site_name of the TOML file, and its site_dir was used. The option -f/--config-file selects a file explicitly, and with no config file in the current folder the command stops with Error: No config file found in the current folder. The official documentation does not state this precedence, so pass -f when both files exist and the choice matters.

The project scope at a glance

The settings below are the ones the basics page of the configuration reference lists. Each row gives the TOML key, the mkdocs.yml key, and what the setting does.

zensical.toml mkdocs.yml Purpose

site_name

site_name

Required. The name of the site, used in the HTML head and in page headers.

site_url

site_url

Canonical URL of the site. Set it unless you build for offline use.

site_description

site_description

Fallback description in the HTML head for pages without their own metadata.

site_author

site_author

Author, written to the HTML head.

copyright

copyright

Notice inserted in the footer; plain text or an HTML fragment.

docs_dir

docs_dir

Directory with the source files. Default docs.

site_dir

site_dir

Directory the site is written to. Default site.

use_directory_urls

use_directory_urls

Directory style URLs (default true) or .html file URLs.

exclude_docs

exclude_docs

Files left out of both build and preview.

draft_docs

draft_docs

Files left out of the build but shown in preview.

not_in_nav

not_in_nav

Pages built and searchable but absent from the automatic navigation.

dev_addr

dev_addr

Address and port of the preview server. Default localhost:8000.

watch

watch

Extra paths monitored for changes during preview.

[project.extra]

extra

Arbitrary key/value pairs for templates.

Other keys appear in the file that zensical new writes, for instance nav, repo_url, repo_name, edit_uri, extra_css, extra_javascript and the [project.theme] table. They are covered on their own pages: Navigation, Theming and Customization and Blog, Tags and Plugins.

Syntax differences between TOML and YAML

The translation from mkdocs.yml to zensical.toml is mechanical, but a few shapes differ:

  • Scalars are quoted strings in TOML (site_name = "Pocket Units"), plain scalars in YAML.

  • Nested mappings become dotted tables: extra: with children turns into [project.extra].

  • A navigation list of one-key mappings becomes an array of inline tables: nav = [{ "Home" = "index.md" }].

  • The three file pattern settings (exclude_docs, draft_docs, not_in_nav) are multi-line strings in both formats: a YAML block scalar (|) in mkdocs.yml and a triple quoted string in TOML. A TOML array is rejected (see File patterns).

The two files below configure the same Pocket Units site:

zensical.toml
[project]
site_name = "Pocket Units"
site_url = "https://example.org/pocket-units/"
site_description = "A tiny units-conversion library."
docs_dir = "docs"
site_dir = "public"
exclude_docs = """
/private/
*.tmp
"""
draft_docs = """
/drafts/
*.draft.md
"""
not_in_nav = """
/internal/
"""
mkdocs.yml
site_name: Pocket Units
site_url: https://example.org/pocket-units/
site_description: A tiny units-conversion library.
docs_dir: docs
site_dir: public
exclude_docs: |
  /private/
  *.tmp
draft_docs: |
  /drafts/
  *.draft.md
not_in_nav: |
  /internal/

Both files built the same four pages with zensical build:

Build started
No issues found
Build finished in 0.28s

Required and unknown keys behave as follows in 0.0.72:

  • Omitting site_name stops the build with Error: Missing required setting: site_name.

  • A key Zensical does not know (a made-up bogus_key in [project]) was accepted silently, as was a file without a [project] table. Typos in top-level keys therefore do not produce an error, so check the output instead of trusting a green build.

site_url and why it matters

site_url is the canonical URL of the published site. Zensical writes it into the HTML head as the canonical link, uses it to build the absolute URLs of the sitemap, and uses its path as the base path of the preview server. The documentation says to set it unless you build for offline use.

[project]
site_name = "Pocket Units"
site_url = "https://example.org/pocket-units/"

With that value, the build of guides/convert.md produced a canonical link of https://example.org/pocket-units/guides/convert/ and a sitemap.xml whose <loc> entries all start with https://example.org/pocket-units/. Two consequences are worth knowing:

  • When the site is hosted below a path, as on GitHub Pages project sites (https://user.github.io/repo/), the path must be part of site_url. Otherwise canonical links and sitemap entries point to the wrong place.

  • zensical serve mounts the site under the path of site_url. With the value above the preview was served at http://localhost:8000/pocket-units/, while a request for / answered with a redirect (HTTP 302) to that path. Use a local site_url or an empty-path URL while developing if that is inconvenient.

When use_directory_urls is false, which Zensical also sets automatically when building for offline use, the canonical link ends in the file name (.../guides/convert.html) and the pages are written as convert.html instead of convert/index.html.

docs_dir and site_dir

docs_dir is the directory with the source Markdown files and assets, and site_dir is where the generated site is written. Both must be relative paths and are resolved relative to the configuration file, not the current directory.

[project]
site_name = "Pocket Units"
docs_dir = "docs"
site_dir = "public"

In the test run:

  • With site_dir = "public", the site, search.json and sitemap.xml were written to public/. The default is site, which is also the path that the generated GitHub Pages workflow uploads, so change both together if you rename it (see Validation and Publishing).

  • A docs_dir that does not exist stops the build with Error: Docs directory does not exist: followed by the resolved path.

  • An absolute path outside the project is refused: Error: docs_dir must be within project root and Error: site_dir must be within project root.

  • The command line option --site-dir of MkDocs is not supported. Set site_dir in the file instead.

The official reference states that docs_dir cannot be set to . (a temporary limitation, with the advice to keep the sources in a subdirectory such as docs). Version 0.0.72 did build with docs_dir = ".", but it then treated the whole project as documentation: the configuration files, the Python package and previous output were copied into the site directory. Treat that as unsupported behaviour and keep a dedicated directory.

Keep site_dir out of docs_dir, and add it to .gitignore. A leftover generated site inside the source tree would be processed as content. If output looks stale after an upgrade, zensical build --clean clears the cache.

File patterns

exclude_docs, draft_docs and not_in_nav select files with the same pattern syntax. The value must be a single string with one pattern per line; a TOML array is rejected with Error: 'exclude_docs' must be a multiline string. Patterns follow .gitignore rules and are resolved relative to docs_dir:

Pattern Matches

/private.md

Only that file at the root of docs_dir (a leading slash anchors the pattern).

private.md

A file of that name at any depth, so also guides/private.md.

drafts/

A directory of that name, at any depth, with its contents (a trailing slash selects directories).

*.md

Wildcards: every Markdown file.

guides/**/*.md

Markdown files at any depth below guides/.

!drafts/keep.md

A leading ! reverses an earlier match, so drafts/keep.md is restored after drafts/.

# comment

Blank lines and lines starting with # are ignored. Escape a literal leading # or ! as \# or \!.

Patterns are evaluated in order and the last matching pattern decides. Negation only works inside one setting: an exclude_docs match cannot be reversed by a negated not_in_nav pattern. The patterns apply to Markdown pages and also to other files in docs_dir, such as images, which is how assets used only by drafts can be kept out of the build.

The test tree used for the next sections is:

docs/
  index.md
  guides/convert.md
  guides/notes.draft.md
  drafts/wip.md
  private/plans.md
  internal/changelog.md
  scratch.tmp
  .hidden.md
Matrix of what exclude_docs, draft_docs and not_in_nav change

The matrix summarises the three settings: a page matched by exclude_docs disappears everywhere, a draft is only dropped from the built site, and a page in not_in_nav is kept everywhere except the automatic navigation.

exclude_docs

exclude_docs removes files from both zensical build and zensical serve. A page matched here cannot be opened, is not searched and is not in the sitemap.

[project]
exclude_docs = """
/private/
*.tmp
"""

The build wrote index.html, guides/convert/index.html and internal/changelog/index.html (plus 404.html), and neither private/plans/ nor scratch.tmp existed in public/. During zensical serve, a request for /private/plans/ answered HTTP 404.

Three kinds of file are excluded by default, before your patterns are applied: dotfiles, files inside dot directories, and the top-level templates/ directory. Reverse a default with !:

[project]
exclude_docs = """
/private/
*.tmp
!.*
"""

With !.* added, .hidden.md was built to public/.hidden/index.html. Use this for files such as a .well-known folder, and remember that dot directories you do not want published (for example .git copies or editor folders) are then no longer protected.

Because the pattern list is replaced rather than extended, every path that must stay hidden has to remain listed.

draft_docs

draft_docs marks work in progress. Matched files are left out of zensical build but included in zensical serve, so authors can preview them while the published site stays clean. No files are drafts by default.

[project]
draft_docs = """
/drafts/
*.draft.md
"""

Observed behaviour:

  • zensical build produced no drafts/ or guides/notes.draft/ output.

  • Under zensical serve, drafts/wip/ and guides/notes.draft/ returned HTTP 200. The served page contained a draft marker with the title text "This page will not be included in the built site.", and the generated search.json and sitemap.xml listed the drafts in that preview.

  • The drafts were not listed in the automatically generated navigation, even during preview. Open them by URL, or add them to an explicit nav for the preview (see Navigation).

  • The patterns also apply to assets, so an image referenced only by drafts can be matched by draft_docs to keep it out of the built site.

A file matched by both exclude_docs and draft_docs is excluded, as the next section explains.

not_in_nav

not_in_nav omits pages from the automatically generated navigation while still building them. Such pages stay in search results and in the sitemap, which makes the setting suitable for changelogs, legal pages or landing pages that are reached by direct links.

[project]
not_in_nav = """
/internal/
changelog.md
"""

In the test run, internal/changelog/index.html was built, appeared in search.json and sitemap.xml, and was absent from the navigation of index.html. Two limits are documented: a matched page can still be added to an explicit nav by name, and the setting is not applied to navigation generated by the awesome-nav plugin.

Precedence between the three settings

Files matched by exclude_docs are excluded even if draft_docs or not_in_nav also match them. The table summarises what each setting changes for a matched page.

Setting zensical build zensical serve Automatic navigation Search and sitemap

exclude_docs

absent

absent

absent

absent

draft_docs

absent

present, with draft marker

absent

present only in preview

not_in_nav

present

present

absent

present

Other project settings

The remaining settings of the basics page are short:

  • site_description, site_author and copyright: metadata and footer text. With them set, the test page contained name="description" content="A tiny units-conversion library." and name="author" in its head. copyright accepts an HTML fragment such as © 2026 Pocket Units contributors.

  • use_directory_urls: true (default) writes guides/convert/index.html and serves it as /guides/convert/; false writes guides/convert.html. MkDocs' --use-directory-urls command line flag is not supported; set the key.

  • dev_addr: address and port for zensical serve, default localhost:8000. The command line option -a/--dev-addr <IP:PORT> overrides it for a single run (zensical serve -a localhost:8123 served the site there).

  • watch: extra files or directories (relative to the configuration file) whose change triggers a full rebuild. Everything in docs_dir, theme files, snippet and macro include paths and the paths of the mkdocstrings compatibility extension are watched without configuration. Symbolic links are followed only when their targets are inside a watched directory, so add the directory of an outside target to watch.

  • [project.extra]: free-form values for templates. They are only used if you override templates (see Theming and Customization).

[project]
site_name = "Pocket Units"
site_url = "https://example.org/pocket-units/"
site_author = "Pocket Units contributors"
copyright = "&copy; 2026 Pocket Units contributors"
use_directory_urls = false
dev_addr = "localhost:3000"
watch = ["pocket_units"]

[project.extra]
library_version = "0.3.0"

This file built without issues; the page canonical link then ended in guides/convert.html. The dev_addr and library_version values are only observable through the preview server and a template, which this run did not check.

Pitfalls and version notes

  • docs_dir set to . is documented as unsupported (and 0.0.72 mixes project files into the site); use a subdirectory.

  • The three pattern settings take a multi-line string, not a list. Mixing styles copied from other tools gives the must be a multiline string error.

  • exclude_docs always wins over draft_docs and not_in_nav, and a ! rule never crosses from one setting to another.

  • Patterns are anchored to docs_dir, not to the project root. After changing docs_dir, review leading slashes.

  • Settings of the mkdocs.yml that Zensical does not support yet are ignored rather than rejected; the official list of unsupported settings currently names hooks. See Migrating from Material for MkDocs.

  • Keep site_url accurate: it affects canonical links, sitemap and the preview base path.

  • The [project] table is expected to be split into further scopes in later releases, with automatic refactorings.