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