Configuration

Layouts, navigation, site variables, forms, auth, and extensions.

lazysite can be configured two ways. The manager is a menu-driven web interface - the easiest way to change most settings, with nothing to edit by hand. This page covers the other way: the configuration files themselves, which is what the manager writes to underneath. Systems and AI agents can also configure a site over the control API and the AI connector (MCP).

lazysite.conf

lazysite/lazysite.conf defines site-wide variables available in layout.tt and all page bodies. It is a plain text file with one key-value pair per line.

Minimal example

site_name: My Site
site_url: ${REQUEST_SCHEME}://${SERVER_NAME}

site_url uses Apache CGI environment variables set automatically on every request. Do not hardcode the domain - the same lazysite.conf works on staging and production.

Value types

# Literal string
site_name: My Site

# Environment variable (CGI - allowlisted vars only)
site_url: ${REQUEST_SCHEME}://${SERVER_NAME}

# Remote URL fetch (trimmed, cached with page TTL)
version: url:https://raw.githubusercontent.com/example/repo/main/VERSION

# Directory scan (array of page metadata)
blog_pages: scan:/blog/*.md sort=date desc

Recognised keys

site_name
Display name of the site.
site_url
Full URL of the site. Typically ${REQUEST_SCHEME}://${SERVER_NAME}.
layout
Active layout. The processor resolves it to lazysite/layouts/NAME/layout.tt. May also be a full remote URL.
theme
Active theme. Must declare the active layout in its theme.json's layouts[]. The processor resolves it to lazysite/layouts/LAYOUT/themes/THEME/theme.json.
layouts_repo
GitHub OWNER/REPO source for the manager's release browser. Default unset hides the browser.
nav_file
Navigation file path, docroot-relative. Default: lazysite/nav.conf.
search_default
Site-wide default for the search: front matter key. true (default) or false.
manager
enabled or disabled. Controls the built-in manager at /manager.
manager_path
URL path for the manager. Default: /manager.
manager_groups (retired - SM138)
Retired. Manager access is the ui capability granted through a group on the Groups page; on upgrade any group this key named receives its capabilities explicitly and the line is removed.
update_channel
The minimum release maturity the site accepts on upgrade, on the edge < beta < stable < certified ladder: stable installs supported releases; certified only builds whose compliance records were walked, beta takes beta and stable builds, any other value (the default) accepts everything. Out-of-channel upgrades are skipped and audited. Set with install.pl --channel edge|beta|stable|certified --docroot ... or from Manager → Site settings. See Update channel.
update_policy
auto or manual (default). Whether a fleet-wide lazysite upgrade --all run (deb-managed hosts) touches this site at all: manual sites are skipped and upgraded only deliberately. Set with install.pl --policy auto|manual --docroot ... (audited as policy-set).
log_level
ERROR, WARN, INFO (default), or DEBUG.
log_format
text (default) or json.
extensions
List of extension script paths to pre-enable without using the manager UI. plugins is the older spelling of this key and still opens the same list - a site keeps whichever it has, and carrying both gives one list rather than two that could disagree.
auth_default
Site-wide default for the auth: front matter key. required, optional, or none (default).
auth_header_user, auth_header_name, auth_header_email, auth_header_groups
Override HTTP header names when using an external auth proxy. Defaults: X-Remote-User, X-Remote-Name, X-Remote-Email, X-Remote-Groups.
webdav_enabled
true or false (default). Master switch for the /dav WebDAV publishing endpoint. While off, /dav returns 404. See WebDAV publishing.
dav_allow_insecure
true or false (default). Permit WebDAV Basic auth without HTTPS (for a TLS-terminating proxy or trusted LAN). Loopback is always allowed; leave off otherwise.
alias_hosts
Comma-separated list of extra hostnames that serve this site as domain aliases, e.g. alias_hosts: blog.example.com, brand2.example. See Domain aliases below.
alias.<host>.<key>
Per-alias-host override for a whitelisted presentation key. See Domain aliases below.

All other keys become TT variables available in page content and the view template.

Example

site_name: My Site
site_url: ${REQUEST_SCHEME}://${SERVER_NAME}
theme: default
nav_file: lazysite/nav.conf
search_default: true
log_level: INFO
log_format: text
manager: enabled
manager_path: /manager
extensions:
  - lazysite-auth.pl
  - plugins/form-handler.pl

Allowlisted environment variables

Only these CGI variables may be used with ${VAR} syntax: SERVER_NAME, REQUEST_SCHEME, SERVER_PORT, HTTPS, REDIRECT_URL, DOCUMENT_ROOT, SERVER_ADMIN.

HTTP_HOST is intentionally excluded - it is request-supplied and therefore untrusted. Use SERVER_NAME for host-based URL construction.

Domain aliases

A domain alias is an additional host that serves the same site - same files, users, and extensions - with its own look: site name, theme (or layout), and navigation. Declare the hosts, then override per host:

alias_hosts: brand2.example, blog.example.com
alias.brand2.example.site_name: Brand Two
alias.brand2.example.theme: dark
alias.brand2.example.nav_file: lazysite/brand2-nav.conf

Rules:

The web server must route the alias hosts to the same docroot (an Apache/nginx server alias). Registering the domain in lazysite is only the lazysite half; DNS, the server alias and TLS are a precondition handled outside lazysite (your control panel / Hestia).

Multilingual language sets

A multilingual site is a set of hosts - one per language - linked by a shared lang_group. Each language is a first-class domain with its own content root, so the languages are authored and served independently under their own hosts.

lang: en
lang_group: providers
content_root: sites/en

alias_hosts: fr.example.com, th.example.com
alias.fr.example.com.lang: fr
alias.fr.example.com.lang_group: providers
alias.fr.example.com.content_root: sites/fr
alias.th.example.com.lang: th
alias.th.example.com.lang_group: providers
alias.th.example.com.content_root: sites/th

Adding a new language is an operator + DNS act - not something an agent can do on its own:

  1. Point DNS (and TLS) for the new host at this server and add the web-server server-alias. This is outside lazysite (control panel / Hestia; a wildcard record + certificate covers every sub-domain at once).
  2. Register the host as a domain with its own content_root (a new folder - the Domains page or lazysite-domains add), and set its lang plus the shared lang_group.
  3. Populate that content root: copy the source language's files to the same paths and translate the values (never the keys, paths, or structure). The lang-status control-API action reports what is missing or stale per language, so a re-run translates exactly the gap.

A content-capable AI agent can do step 3 (the translation) on its own; steps 1-2 need the operator, because DNS and domain registration sit outside the translation surface.

lazysite/nav.conf defines the site navigation. The processor reads it into a nav TT variable available in every page.

Format

Navigation is YAML. Items may be links, group headings, or groups with nested children:

- label: Home
  url: /
- label: About
  url: /about
- label: Docs
  children:
    - label: Install
      url: /docs/install
    - label: Authoring
      url: /docs/authoring
- label: Resources
  children:
    - label: GitHub
      url: https://github.com/example

Rules:

Legacy pipe format

An older pipe-separated format is also accepted:

Home | /
About | /about
Docs | /docs/
  Installation | /docs/install

nav is an array of hashrefs. Each item has label, url, and children keys. See Views for looping examples.

If nav.conf is missing, nav is an empty array and the template renders without navigation.

Alternate nav file

Override the default path in lazysite.conf:

nav_file: lazysite/docs-nav.conf

The path is relative to the docroot.

Authentication

Authentication is configured through three mechanisms:

See Authentication for full details.

Forms

Forms are configured in files under lazysite/forms/:

FORMNAME.conf
Per-form configuration. Lists the handlers it calls, by ID.
handlers.conf
The handlers - named functions a form or the schedule calls. Each has an id, type (smtp, file, table or connector), name, and its type's settings. Edited through the Handlers page, the control API, MCP or lazysite-handlers.pl, where the handler's destination decides the capability.
schedule.conf
What the timer calls: a handler, an interval, fixed fields.
smtp.conf
SMTP connection settings shared by all SMTP-type handlers.

See Forms and Forms SMTP for full details.

Extensions

Extensions are CGI scripts and tools that register themselves with the manager through a --describe JSON protocol. They extend the core rather than plug into it: the renderer requires nothing from them, and every one ships in the package rather than being installed. Enabled extensions appear on the Extension Manager page.

Auto-discovery scans cgi-bin/ and tools/ for scripts supporting --describe. Enable or disable from the Extension Manager page.

To pre-enable without the manager, list scripts in lazysite.conf:

extensions:
  - lazysite-auth.pl
  - plugins/form-handler.pl
  - plugins/audit.pl

URL remapper

For a site that has replaced another on the same hostname while links to the old one are still in the world - notifications, invoices, helpdesk tickets. A rule sends a path prefix, and everything under it, to a destination you name, keeping the rest of the path and the query string:

/web/order?id=42   ->   https://backend.example.com/order?id=42

It answers only when nothing else does: a real page and an alias both win, so a rule can cover an old link and can never shadow content. Prefixes match on a path boundary - /web covers /web and /web/login, never /website-terms. 302 unless a rule asks for 301; a migration's destination can move, and a cached permanent redirect to a host that later moves is a trap.

Switch it on on the Extension Manager page (it ships off). Set a domain's rules with the remap-save action, which needs Domains & site packages and replaces that one domain's rules:

{"host": "www.example.com",
 "rules": [ {"prefix": "/web", "destination": "https://backend.example.com"},
            {"prefix": "/helpdesk", "destination": "https://backend.example.com/support", "code": 301} ]}

An empty rules list removes the domain's rules. remap-list returns them. A manager confined to one domain may set rules for that domain only, and never for a host that is not a registered domain.

The destination always comes from the rules, never from the request - there is no ?to=, and there will not be one, because that would make the site an open redirect.

When can a rule go? Rules and use, on the extension's own row on the Extension Config page, shows how often each rule was followed and when it was last used, read from the visitor log. When the last-used date stops moving, the links it covered have stopped arriving. If the visitor log is not recording, the report says so rather than showing zero - "nobody followed it" and "nothing was recorded" are different answers, and only the first means a rule can go.

Logging

Log level and format are set in lazysite.conf:

log_level: INFO    # ERROR, WARN, INFO, DEBUG
log_format: text   # text or json

Both can be overridden at startup with environment variables:

LAZYSITE_LOG_LEVEL=DEBUG perl tools/lazysite-server.pl ...
LAZYSITE_LOG_FORMAT=json perl tools/lazysite-server.pl ...

To also forward log streams to syslog for an external collector (the "Logging & forwarding" extension manages these keys):

forward_audit: off        # audit-trail entries -> syslog, INFO priority
forward_diagnostics: off  # application log events -> mapped priority
syslog_facility: daemon   # or local0..local7

Forwarding is best-effort and never blocks the site; the files under lazysite/logs/ remain the record.

Layouts and themes

Activate a layout and theme by name in lazysite.conf:

layout: default
theme: odcc

The processor resolves the layout to lazysite/layouts/default/layout.tt and the theme to lazysite/layouts/default/themes/odcc/theme.json. Theme assets are web-served from /lazysite-assets/default/odcc/ (nested).

A theme must declare the active layout in its theme.json's layouts[] array; mismatched themes render layout-only with a warning.

See Layouts and themes for installation and authoring.

Page scan

The scan: prefix in lazysite.conf or tt_page_var scans a directory and returns an array of page metadata as a TT variable.

blog_pages: scan:/blog/*.md

In a page body:

[% FOREACH post IN blog_pages %]
## [% post.title %]
[% post.subtitle %] - [% post.date %]
[% END %]

Pattern rules

Page object fields

Each item in the returned array carries these built-in fields:

Custom keys pass through. Any other (non-control) front-matter key is exposed on the page object under the same name, so a registry card can be self-describing:

# /gallery/nova.md
title: NOVA
kind: Statement
demo: /nova
accent: "#7C5CFF"
order: 2

[% FOREACH t IN gallery %][% t.kind %] - [% t.demo %] - [% t.accent %][% END %]

Surrounding quotes are stripped (so accent: "#7C5CFF" yields #7C5CFF - quote a value that starts with #, since a bare # … is a YAML comment), and TT markers are stripped for safety. Control keys (layout, theme, auth, register, search, tt_*) are not passed through.

Sort order

Default sort is by filename. Use the sort= modifier to sort by field:

blog_pages: scan:/blog/*.md sort=date desc
gallery:    scan:/gallery/**/*.md sort=order asc

Built-in sort fields are date, title, filename; any custom key also sorts (e.g. sort=order), and numeric values compare numerically (2 before 10), not lexically. Direction: asc or desc (default asc).

For reverse-chronological blog posts, use sort=date desc. Date-prefix filenames (2026-03-20-post-title.md) also sort chronologically by filename without needing the sort modifier.

Per-page scan

Scan variables work in tt_page_var for page-scoped results:

tt_page_var:
  section_pages: scan:/services/*.md sort=title asc

Config path override

The default lazysite.conf path can be overridden via a command-line argument or environment variable. This is rarely needed - each site on a server has its own docroot and therefore its own lazysite.conf automatically. See Config path override for details.