Site Configuration
Complete seite.toml reference for your static site: site settings, collections, build options, deployment targets, languages, images, and analytics.
Everything about your seite site is configured in a single seite.toml file. The static site generator uses sensible defaults, so most sections are optional: start with [site] and [[collections]], then add sections as you need them.
Overview
All configuration lives in seite.toml at the project root, created by seite init. Here's a complete example:
[site]
title = "My Site"
description = "A personal blog and documentation"
base_url = "https://example.com"
language = "en"
author = "Jane Doe"
[[collections]]
name = "posts"
paginate = 10
[[collections]]
name = "docs"
[[collections]]
name = "pages"
[build]
output_dir = "dist"
minify = true
fingerprint = true
[deploy]
target = "github-pages"
[languages.es]
title = "Mi Sitio"
description = "Un blog personal"
[images]
widths = [480, 800, 1200]
quality = 80
webp = true
avif = false
lazy_loading = true
[analytics]
provider = "google"
id = "G-XXXXXXXXXX"
cookie_consent = true
[site]
| Field | Type | Default | Description |
|---|---|---|---|
title | string | required | Site title, used in templates and meta tags |
description | string | "" | Site description for SEO |
base_url | string | "http://localhost:3000" | Full base URL for canonical links, sitemap, RSS |
language | string | "en" | Default language code |
author | string | "" | Author name for JSON-LD and RSS |
Set base_url to your real domain before deploying. Leaving it as localhost will trigger a pre-flight warning and produce incorrect canonical URLs, sitemaps, and RSS feeds.
[[collections]]
Each [[collections]] entry defines a content collection. See Collections for full details.
[[collections]]
name = "posts"
label = "Blog"
directory = "posts"
url_prefix = "/posts"
default_template = "post.html"
has_date = true
has_rss = true
listed = true
private = false # optional: exclude from discovery; password-protect with [access]
access_group = "members" # optional: password group (defaults to collection name)
nested = false
paginate = 10
subdomain = "blog" # optional: deploy to blog.example.com
subdomain_base_url = "https://blog.example.com" # optional: explicit URL override
deploy_project = "my-blog" # optional: Cloudflare/Netlify project for subdomain
When subdomain is set on a collection, it gets its own output directory (dist-subdomains/{name}/), its own base URL (https://{subdomain}.{base_domain}), and its own sitemap, RSS, robots.txt, llms.txt, and search index. The collection is excluded from the main site build. The subdomain root page renders with the collection's own index template (e.g. trust-index.html) and context — exactly like that collection's index page on the main domain. Use subdomain_base_url to override the auto-derived URL (useful when base_url contains www). See Collections for details.
Private collections
Set private = true to keep a collection out of every public discovery surface while still building its hub and pages. Add [access] to enforce password authentication on Cloudflare Pages.
[[collections]]
name = "docs"
private = true
access_group = "staff"
url_prefix = "/docs"
default_template = "doc.html"
When private = true:
- The collection builds fully — every item page and its auto-generated hub/index page render (with the collection's index template and
data.*context), for the default language and every/{lang}/variant. Unlikelisted = false, the hub is not suppressed. - Its pages are excluded from the homepage listing,
sitemap.xml,llms.txt,llms-full.txt,search-index.json, RSS/Atom feeds, and tag pages. - Every page is stamped
<meta name="robots" content="noindex, nofollow">unless it sets its ownrobotsfrontmatter. - Internal links still resolve and the
.mdalternates keep generating (they live under the gated path).
private is independent of listed (the hub renders even when the collection is hidden from the homepage) and composes with subdomain, paginate, and the rest. The build logs how many pages were excluded, e.g. 12 private pages excluded from discovery. Absent or false, behavior is unchanged.
Without [access], private remains a discovery-only setting and does not restrict who can load a URL. This preserves existing configurations.
[access]
Enable password protection for all private = true collections:
[access]
mode = "password"
session_hours = 168
| Field | Type | Default | Description |
|---|---|---|---|
mode | string | required | Access mode; currently "password" |
session_hours | integer | 168 | How long an authenticated browser session remains valid (1–8760) |
Password access currently supports Cloudflare Pages only. During seite build, seite generates an advanced-mode _worker.js that checks the password before serving protected files. Missing secrets fail closed with a service-unavailable response.
The protected scope comes from the collection:
- A non-empty
url_prefixprotects that path and its descendants. - Translated versions of that collection are protected under each configured language prefix.
- An empty prefix protects the entire main domain.
- A private collection with
subdomainprotects the entire subdomain output. - When scopes overlap, the most-specific path wins.
Each private collection's password group defaults to its name. Set access_group to let multiple collections share one password or to give different paths different passwords. Group names may contain ASCII letters, numbers, _, and -.
seite access groups seite access set-password staff seite deploy
The command prompts for the password, then stages it and separate signing secrets for both production and preview on each relevant Cloudflare Pages project through Wrangler. Secrets are not stored in the config, source tree, logs, or command arguments. Deploy from the Pages project's configured production branch to activate production; projects created by seite use main. Run seite deploy --preview to activate preview.
Ordinary static/ files stay public. With [access] enabled, place protected downloads under static/private/<group>/; they are emitted at /private-assets/<group>/ and gated by that group. Image optimization is skipped for this protected tree so derivatives cannot escape into public /static, and the legacy /static/private/ URL namespace is denied. Without [access], those files remain under /static/private/<group>/ and are publicly reachable.
Password access owns the generated Cloudflare _worker.js and _routes.json. Remove custom versions from public/ before enabling it; a conflicting file fails the build rather than risking an authentication bypass.
[build]
| Field | Type | Default | Description |
|---|---|---|---|
output_dir | string | "dist" | Build output directory |
data_dir | string | "data" | Directory for data files (YAML/JSON/TOML) |
public_dir | string | "public" | Directory for root-level files copied to output without prefix (favicon.ico, .well-known/, _redirects, etc.) |
minify | bool | false | Strip CSS/JS comments and collapse whitespace |
fingerprint | bool | false | Add content hash to asset filenames for cache busting |
math | bool | false | Enable math/LaTeX rendering ($inline$ and $$display$$ blocks via KaTeX) |
mermaid | bool | false | Render fenced ```mermaid blocks as Mermaid diagrams (client-side) |
Enable minify for production builds. It strips CSS/JS comments and collapses whitespace for smaller files. Enable fingerprint when your CDN caches aggressively: content hashes in filenames ensure browsers always fetch the latest version.
When fingerprint = true, static files get hashed names (e.g., style.a1b2c3d4.css) and an asset-manifest.json is written to the output directory.
Mermaid diagrams
Set mermaid = true under [build] to render Mermaid diagrams from ordinary fenced code blocks — no shortcode required:
```mermaid graph TD A[Start] --> B{Works?} B -->|Yes| C[Ship it] B -->|No| A ```
Each ```mermaid block is emitted as <div class="mermaid">…</div>, and a small Mermaid loader (from a CDN) is injected only on pages that contain a diagram — pages without one pay nothing. Diagrams render in the browser, so they work with any theme.
When mermaid = false (the default), a ```mermaid block is treated like any other unrecognized language and rendered as a plain code block.
CSS processing
seite does not include a Sass/SCSS/PostCSS preprocessor. This is a deliberate design choice: all theme CSS lives inline in the Tera template file, making themes completely self-contained single files with no external dependencies. This means:
- No build toolchain beyond the
seitebinary itself - Themes are portable: install from a URL, no asset pipeline setup
- AI-generated themes (
seite theme create) produce one complete file
Modern CSS natively supports features that previously required preprocessors: custom properties (variables), nesting, calc(), color-mix(), clamp(). For projects that need Sass, compile externally (sass style.scss static/style.css) and reference the output in your template: seite copies everything in static/ to the output directory.
See Building Custom Themes for more detail on this approach.
Data Files
Place YAML, JSON, or TOML files in the data/ directory to make structured data available in all templates as {{ data.filename }}.
Supported formats
| Extension | Format |
|---|---|
.yaml, .yml | YAML |
.json | JSON |
.toml | TOML |
Nested directories
Subdirectories create nested keys:
data/
nav.yaml → {{ data.nav }}
authors.json → {{ data.authors }}
menus/
main.yaml → {{ data.menus.main }}
footer.yaml → {{ data.menus.footer }}
Theme integration
All bundled themes conditionally render data.nav for header navigation and data.footer for footer links and copyright. Example data/nav.yaml:
- title: Blog url: /posts - title: About url: /about - title: Docs url: /docs
Example data/footer.yaml:
links: - title: GitHub url: https://github.com/user/repo - title: Twitter url: https://twitter.com/user copyright: "2026 My Company"
Conflict detection
The build will error if two data files share the same stem (e.g., authors.yaml and authors.json) or if a file and directory conflict (e.g., nav.yaml and nav/main.yaml). Unknown file extensions are skipped with a warning.
Per-language values
On a multi-language site, any data value may be a language map — a mapping keyed by your configured language codes — and it resolves to the page's language at build time:
purpose: en: "Cloud infrastructure" de: "Cloud-Infrastruktur"
Plain values are unchanged; only maps whose keys are all configured language codes are resolved. See Per-Language Data Values.
[deploy]
| Field | Type | Default | Description |
|---|---|---|---|
target | string | "github-pages" | Deploy target: github-pages, cloudflare, netlify |
repo | string | auto-detected | Git repository URL (GitHub Pages) |
project | string | auto-detected | Project name (Cloudflare Pages) |
auto_commit | bool | true | Auto-commit and push before deploying. On non-main branches, auto-uses preview mode |
[languages.*]
Optional. Add language sections to enable multi-language support:
[languages.es]
title = "Mi Sitio"
description = "Un blog personal y documentación"
[languages.fr]
title = "Mon Site"
Each language can override title and description. See Multi-language for details.
[images]
Optional. When this section is present, seite automatically processes images in static/. When omitted, images are copied as-is with no resizing or rewriting. New projects created with seite init include this section by default.
| Field | Type | Default | Description |
|---|---|---|---|
widths | array | [480, 800, 1200] | Target widths in pixels for resized copies |
quality | int | 80 | JPEG/WebP quality (1-100) |
webp | bool | true | Generate WebP variants alongside originals |
avif | bool | false | Generate AVIF variants alongside originals (better compression than WebP) |
avif_quality | int | 70 | AVIF quality (1-100). Lower than WebP is fine — AVIF compresses better |
lazy_loading | bool | true | Add loading="lazy" to <img> tags |
When configured, images in static/ are resized to each width, optionally converted to WebP and/or AVIF, and <img> tags in HTML are rewritten with srcset and <picture> elements. AVIF sources appear first in the <picture> element so browsers that support AVIF use it (best compression), falling back to WebP, then the original format. To disable image processing, remove the [images] section entirely.
[analytics]
Optional. When present, analytics scripts are automatically injected into every HTML page during build. Supports Google Analytics 4, Google Tag Manager, Plausible, Fathom, and Umami.
| Field | Type | Default | Description |
|---|---|---|---|
provider | string | required | Analytics provider: google, gtm, plausible, fathom, umami |
id | string | required | Measurement/tracking ID (e.g., G-XXXXXXX, GTM-XXXXX, domain, or site ID) |
cookie_consent | bool | false | Show a cookie consent banner and gate analytics on user acceptance |
script_url | string | varies | Custom script URL (required for self-hosted Umami, optional for others) |
extensions | array | [] | Plausible script extensions (e.g., ["tagged-events", "outbound-links"]). Ignored for other providers and when script_url is set |
Examples
Google Analytics 4 (direct):
[analytics]
provider = "google"
id = "G-XXXXXXXXXX"
Google Analytics 4 with cookie consent banner:
[analytics]
provider = "google"
id = "G-XXXXXXXXXX"
cookie_consent = true
Google Tag Manager:
[analytics]
provider = "gtm"
id = "GTM-XXXXXXX"
cookie_consent = true
Plausible Analytics (privacy-friendly, no cookies):
[analytics]
provider = "plausible"
id = "example.com"
For new installations, Plausible now provides a unique snippet per site from your dashboard (Settings → Installation). Use script_url to point to it — features like tagged events, outbound links, and file downloads are toggled in your Plausible site settings and automatically included. seite generates the correct async script tag plus the plausible.init() bootstrapper for custom event support:
[analytics]
provider = "plausible"
id = "example.com"
script_url = "https://plausible.io/js/pa-XXXXXXXXXX.js"
Legacy: Plausible with extensions (pre-October 2025 script)
The old extension-based approach still works for existing setups but won't receive new Plausible features:
[analytics]
provider = "plausible"
id = "example.com"
extensions = ["tagged-events", "outbound-links", "file-downloads"]
The extensions field appends names to the script filename (e.g., script.tagged-events.outbound-links.js). New sites should use script_url with the unique snippet from the Plausible dashboard instead. See the Plausible script update guide.
Fathom Analytics:
[analytics]
provider = "fathom"
id = "ABCDEF"
Self-hosted Umami:
[analytics]
provider = "umami"
id = "abc-def-123"
script_url = "https://stats.example.com/script.js"
Privacy-respecting analytics like Plausible, Fathom, and Umami don't use cookies. You can typically use them without a consent banner (cookie_consent = false). Google Analytics and GTM set cookies and may require consent under GDPR/ePrivacy: set cookie_consent = true for those.
Cookie consent banner
When cookie_consent = true, a fixed-position banner appears at the bottom of the page on the visitor's first visit. Analytics scripts only load after the visitor clicks "Accept". The choice is stored in localStorage so it persists across visits. The banner is fully accessible with role="dialog", keyboard-navigable buttons, and responsive design.
How it works
Analytics injection happens as a post-processing step after the main build. Every .html file in the output directory is rewritten:
- Without consent: the analytics
<script>tag is injected before</head>. GTM also gets a<noscript>fallback after<body>. - With consent: a consent banner with Accept/Decline buttons is injected before
</body>. Analytics scripts are loaded dynamically only after the user accepts.
To remove analytics, delete the [analytics] section from seite.toml.
Frontmatter
Content files use YAML frontmatter between --- delimiters:
--- title: "Page Title" date: 2026-02-18 updated: 2026-02-19 description: "Used in meta tags, OG, Twitter Cards, JSON-LD" image: /static/preview.jpg slug: custom-slug tags: - rust - web draft: true template: custom.html robots: noindex extra: hero: true color: "#4361EE" ---
| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Page title |
date | date | Posts only | Publication date (YYYY-MM-DD) |
updated | date | No | Last modified date |
description | string | No | SEO description |
image | string | No | Social preview image |
slug | string | No | Override auto-generated slug |
tags | list | No | Content tags |
draft | bool | No | Exclude from build unless --drafts |
template | string | No | Override default template |
robots | string | No | Per-page robots directive |
extra | map | No | Arbitrary data for templates |
seite-workspace.toml
For multi-site setups, a seite-workspace.toml at the workspace root configures all sites. Each site still has its own seite.toml.
[workspace]
name = "my-workspace"
shared_data = "data" # Shared data directory (optional)
shared_static = "static" # Shared static assets (optional)
shared_templates = "templates" # Shared templates (optional)
[[sites]]
name = "blog"
path = "sites/blog"
# base_url = "https://blog.example.com" # Override site base_url
# output_dir = "dist/blog" # Override output location
[[sites]]
name = "docs"
path = "sites/docs"
[cross_site]
unified_sitemap = false # Combine all sites into one sitemap
cross_site_links = false # Validate links across sites
unified_search = false # Combined search index
See Workspaces for the full guide.
Next Steps
- Collections: configure how posts, docs, and pages behave
- Templates & Themes: use config values and data files in your templates
- Deployment: deploy with the settings you've configured
- Workspaces: manage multiple sites in a single repository