Configuration reference
The include system is configured under [include] in bext.config.toml. Global defaults, per-source rules, security limits — all declarative, all reloadable without code changes.
[include]
enabled = true
[include.cache]
enabled = true
l1_max_entries = 5000
max_variants_per_include = 100
[[include.rules]]
pattern = "/__bext/nav*"
ttl = "5m"
swr = "1h"
tags = ["chrome"]
This page is the full reference. See the topic-specific pages for how individual features work.
[include] — master switch
[include]
enabled = true # set false to disable the whole subsystem
When disabled, <bext-include>, SSI, and ESI tags all pass through unchanged — no parsing, no resolution.
[include.cache] — tiered cache tuning
[include.cache]
enabled = true # default when any TTL is declared
l1_max_entries = 5000 # in-process LRU cap
l2_prefix = "bext:include:" # Redis key prefix (when L2 is configured)
max_variants_per_include = 100 # cardinality cap per source
default_ttl = "0" # no cache by default
default_swr = "0" # no SWR by default
[include.cache.adaptive]
base_ttl_ms = 60_000 # 60s base for adaptive TTL
max_ttl_ms = 600_000 # 10min cap
extension_threshold = 10 # start extending after N hits
See Caching includes.
[include.vary] — dimension declarations
[include.vary]
dimensions = ["role", "locale", "geo", "device", "theme", "currency"]
[include.vary.custom]
"preferred-unit" = { source = "cookie", name = "unit" }
"api-version" = { source = "header", name = "x-api-version" }
Only dimensions listed here can be referenced in vary="..." attributes. Add custom dimensions from cookies or headers. See Vary-aware caching.
[include.files] — filesystem partials
[include.files]
enabled = true
root = "_partials" # relative to site root
max_size = "1MB"
allowed_extensions = ["html", "htm", "svg", "txt"]
enforce_jail = true # block symlinks outside root
See File partials.
[include.v8] — component rendering
[include.v8]
enabled = true # requires v8 feature
max_execution_ms = 500
max_memory_mb = 64
pool_size = 4 # pre-warmed isolates
See Component rendering.
[include.circuit_breaker] — failure handling
[include.circuit_breaker]
enabled = true
failure_threshold = 5 # failures before opening circuit
cooldown = "30s" # time before retrying
half_open_requests = 1 # test requests in half-open state
Per-source circuit breaker. See Timeout & Fallback.
[include.timeouts] — default timeouts
[include.timeouts]
default = "5s" # global fallback
proxy = "3s" # upstream HTTP fetches
v8 = "500ms" # V8 renders
Per-include timeout="..." overrides these defaults.
[include.security] — limits and allow/deny
[include.security]
max_depth = 3 # nested include depth limit
max_includes_per_page = 50 # per-response cap
allowed_sources = [] # empty = all allowed
blocked_sources = [] # always denied
Nested include depth
An include can reference a source that itself contains includes. The depth counter prevents runaway recursion:
Page -> /_partials/layout.html (depth 1)
-> /_partials/header.html (depth 2)
-> /_partials/logo.html (depth 3)
-> /_partials/svg-defs.html (depth 4 — REJECTED with max_depth=3)
Exceeding the limit emits a warning and replaces the include with an empty string.
Per-page count
The max_includes_per_page counter prevents a malicious or broken template from causing thousands of resolutions:
max_includes_per_page = 50
After 50 includes on a single response, additional tags are replaced with empty strings. Adjust based on your page complexity — most pages have 5-15 includes.
Allow/deny lists
[include.security]
# Allowlist mode: only listed patterns are permitted
allowed_sources = [
"/__bext/*",
"/_partials/*",
"/cms/*",
]
# Denylist: always blocked, even if in allowed_sources
blocked_sources = [
"/admin/secrets/*",
"/.env*",
]
Evaluation order:
1. If src matches any pattern in blocked_sources, reject.
2. If allowed_sources is non-empty and src doesn't match any pattern, reject.
3. Otherwise, permit.
Empty allowed_sources means "all permitted except blocked" (default). Non-empty means strict allowlist.
[[include.rules]] — per-source configuration
Per-pattern defaults that apply to any include (any syntax) whose src matches.
[[include.rules]]
pattern = "/__bext/nav*"
ttl = "5m"
swr = "1h"
tags = ["chrome"]
[[include.rules]]
pattern = "/_partials/footer*"
ttl = "1d"
tags = ["chrome"]
[[include.rules]]
pattern = "/api/*"
timeout = "200ms"
fallback = "/_partials/api-fallback.html"
circuit_breaker = true
[[include.rules]]
pattern = "/partials/pricing*"
vary = ["role", "locale"]
ttl = "10m"
[[include.rules]]
pattern = "/partials/admin-*"
auth = "required"
roles = ["admin"]
vary = ["role"]
ttl = "30m"
Rule fields
All fields are optional; omitted fields fall back to global defaults.
| Field | Type | Notes |
|---|---|---|
pattern |
glob string | required — matched against include's src |
ttl |
duration | cache freshness |
swr |
duration | stale-while-revalidate window |
tags |
string array | cache invalidation tags |
vary |
string array | cache vary dimensions |
auth |
required, anonymous |
authentication gate |
roles |
string array | role requirements |
timeout |
duration | resolution timeout |
fallback |
string | fallback source path |
adaptive_ttl |
bool | use adaptive-TTL formula |
circuit_breaker |
bool | enable per-source circuit |
Rule matching
- First matching rule wins — ordering in the TOML file matters.
- Glob syntax: * matches any characters except /; ** matches across path separators.
- Attribute overrides rule: if the HTML element specifies ttl="1h", that wins over a rule's ttl="5m".
Examples
Cache everything under /__bext/ for 5 minutes with chrome tag:
[[include.rules]]
pattern = "/__bext/*"
ttl = "5m"
tags = ["chrome"]
Separate rules for different API endpoints:
[[include.rules]]
pattern = "/api/live/*"
ttl = "10s"
timeout = "100ms"
[[include.rules]]
pattern = "/api/*" # catch-all AFTER specific rules
ttl = "1m"
timeout = "300ms"
Role-based pricing with separate rules per role tier:
[[include.rules]]
pattern = "/partials/pricing/enterprise*"
auth = "required"
roles = ["enterprise"]
vary = ["role"]
ttl = "1h"
[[include.rules]]
pattern = "/partials/pricing/*"
ttl = "10m"
vary = ["role"]
[[experiments]] — A/B experiment definitions
[[experiments]]
name = "hero-redesign"
cookie = "bext_exp_hero"
cookie_ttl = "30d"
variants = [
{ name = "control", weight = 50, src_suffix = "" },
{ name = "variant-a", weight = 25, src_suffix = "-v2" },
{ name = "variant-b", weight = 25, src_suffix = "-v3" },
]
See Experiment variants.
Route-rule integration
Include config can be co-located with route rules for page-specific overrides:
[[route_rules]]
pattern = "/blog/*"
rendering = "isr"
revalidate = 3600
[route_rules.includes]
"/__bext/nav" = { ttl = "1h" }
"/partials/sidebar" = { loading = "lazy", ttl = "10m" }
"/partials/comments" = { timeout = "500ms", fallback = "/_partials/comments-fallback.html" }
On blog pages, these include defaults apply. On other pages, the global [[include.rules]] apply.
Environment variable interpolation
Config values support ${VAR} and ${VAR:-default}:
[include.v8]
pool_size = "${BEXT_V8_POOL_SIZE:-4}"
[include.circuit_breaker]
failure_threshold = "${BEXT_CIRCUIT_THRESHOLD:-5}"
[[include.rules]]
pattern = "/cms/*"
timeout = "${CMS_TIMEOUT:-500ms}"
fallback = "${CMS_FALLBACK:-/_partials/cms-offline.html}"
Useful for environment-specific tuning without forking config.
Config validation
At server startup, bext validates the include config:
- Pattern syntax — valid glob.
- Duration parsing — recognized format.
- Experiment weights — non-zero, variants non-empty.
- Vary dimensions — declared in [include.vary] or built-in.
- Role without auth — warns if roles is set but auth is not.
- Cache without vary (for auth-gated) — warns about poisoning risk.
- Extensions — reasonable defaults enforced.
Errors are fatal — the server won't start with invalid config. Warnings are logged but non-fatal — the server starts and logs the issue.
Reloading config
Include config can be reloaded without restart via SIGHUP (in masquerade mode) or the admin API:
# SIGHUP for config reload
kill -HUP $(cat /run/bext.pid)
# or via admin API
curl -X POST http://localhost/__bext/admin/reload-config
Changes to [include.rules] and [include.cache] apply to new requests without dropping in-flight ones.
Minimal working config
For a quick start, the include system works with zero configuration — sensible defaults ship in the binary. But a typical production config looks like:
[include]
enabled = true
[include.cache]
enabled = true
l1_max_entries = 5000
[include.security]
max_depth = 3
max_includes_per_page = 50
[include.timeouts]
default = "3s"
proxy = "1s"
[[include.rules]]
pattern = "/__bext/*"
ttl = "5m"
tags = ["chrome"]
[[include.rules]]
pattern = "/_partials/*"
ttl = "1h"
tags = ["chrome"]
[[include.rules]]
pattern = "/api/*"
timeout = "500ms"
fallback = "/_partials/api-fallback.html"
See also
- Overview — top-level include system
- bext.config.toml — full config file reference
- Plugin API — registering custom resolvers