Settings reference
Every key of Settings.json with its type, default, effect and an example, the environment overrides, a complete production configuration and the Google Analytics measurement id.
Where the settings live
All configuration is one JSON object in src/Config/Settings.json. The file is not committed: bin/wbe install creates it from src/Config/settings.json.default. The framework reads it once per request; a missing key falls back to the default listed below, and a malformed value in http, analytics or the page cache policy throws an error instead of being ignored. After changing the file clear the cache (POST /api/v2/cache/flush, an integration key with the cache:write scope) and, when renderer.compileCheck is off, the compiled template directory. Check the result with vendor/bin/wbengine.php config:check --config=src/Config/Settings.json --root=src.
Secrets can come from the environment instead of the file: WBENGINE_RECAPTCHA_SECRET, WBENGINE_DEBUG and WBENGINE_IMAGE_DIR. An environment variable that is set and not empty wins over the file.
General
| Key | Type, default | Effect |
|---|---|---|
timeZone |
string, Europe/Prague |
PHP time zone of the application. Required. |
debug |
bool, false |
Shows the debug bar and detailed errors. Never true in production. WBENGINE_DEBUG overrides it. |
Renderer
| Key | Type, default | Effect |
|---|---|---|
renderer.adapterName |
string, NativeRenderer |
NativeRenderer or the optional SmartyRenderer. |
renderer.autoEscape |
bool, true |
Escapes the output of template variables. false is only a migration aid for old templates. |
renderer.templatesDir |
string, /View/ |
Template directory under src/. |
renderer.compiledDir |
string, Compiled/ |
Compiled templates, must be writable by the PHP user. |
renderer.configDir |
string, /Config/ |
Template configuration directory. |
renderer.compileCheck |
bool, true |
Checks on every request whether a template changed. Set false in production and empty the compiled directory on deploy. |
Database
dbAdapterDefinition is an object: adapterName (Mysqli), hostname, port, database, username, password, charset. The default file points to the Docker host db with development credentials; replace all of them. Use utf8mb4 as charset on a new installation. There is no environment override for the database password, so protect the file (chmod 640).
Secrets
| Key | Type, default | Effect |
|---|---|---|
recaptcha.secretKey |
string, empty | Secret of the login form captcha. Empty means the captcha check fails closed. Overridden by WBENGINE_RECAPTCHA_SECRET. With recaptcha.enabled: true the doctor command requires it. |
Proxies and HTTPS
| Key | Type, default | Effect |
|---|---|---|
http.trustedProxies |
array, [] |
Exact addresses or CIDR ranges of reverse proxies whose forwarding headers are believed. Empty means every header is ignored. |
http.forwardedHeaders |
array, [] |
Opt-in headers: x-forwarded-for (client address) and x-forwarded-proto (original scheme). Other values are rejected. |
Behind host Nginx or a load balancer set both, otherwise the site thinks every visitor has the proxy address and uses plain HTTP: session cookies are not secure, rate limit keys are shared and /integrations/ refuses to issue a key. The proxy must overwrite X-Forwarded-Proto and build X-Forwarded-For from the real connection.
"http": { "trustedProxies": ["172.18.0.1/32"], "forwardedHeaders": ["x-forwarded-for", "x-forwarded-proto"] }
Languages
| Key | Type, default | Effect |
|---|---|---|
i18n.enabled |
bool, false (the skeleton sets true) |
Turns languages on. |
i18n.default |
string, en |
Language of unprefixed URLs and fallback for missing translations. |
i18n.locales |
array, ["en"] |
Offered languages, for example ["en", "cs"]. The default is always included. |
i18n.detect |
array, ["url", "cookie", "header"] |
Detection order. The skeleton uses ["url"] so unprefixed URLs are always English. Add cookie or header to let a saved choice or the browser language win. |
i18n.urlPrefix |
bool, true |
Uses /cs/... prefixes for non-default languages. |
i18n.prefixDefault |
bool, false |
Also prefixes the default language (/en/...). |
i18n.cookie |
string, wb_locale |
Name of the preference cookie (letters, digits, underscore). |
i18n.cookieDays |
int, 365 |
Lifetime of that cookie. |
i18n.dir |
string, /Locale/ |
Catalog directory under src/, with en.php, cs.php. |
i18n.baseUrl |
string, empty | Public address such as https://example.com. Used for absolute hreflang links instead of the Host header. Set it in production. |
Caching
| Key | Type, default | Effect |
|---|---|---|
cache.enabled |
bool, false (the skeleton sets true) |
Data and fragment cache. |
cache.driver |
string, file |
file or apcu. apcu needs the extension and a PHP-FPM restart to clear. |
cache.ttl |
int seconds, 300 |
Lifetime of data cache entries. |
cache.dir |
string, temp directory | Directory of the file driver. Default is a subdirectory of the system temp directory, which can vanish under PrivateTmp: set an absolute path. |
cache.pages.enabled |
bool, false |
Whole-page cache for anonymous requests. |
cache.pages.ttl |
int seconds, 60 |
Lifetime of cached pages. |
cache.pages.allowPaths |
array, omitted | Exact canonical paths allowed in the page cache. Omitted allows other eligible paths, empty [] disables page caching. |
cache.pages.denyPaths |
array, omitted | Paths never cached. Wins over allowPaths. |
Paths are compared ignoring case and a trailing slash, invalid policies fail closed. Private, Set-Cookie and personal responses are never stored whatever the policy says. Example: "allowPaths": ["/", "/docs/", "/blog/"], "denyPaths": ["/profile/", "/login/"]. A broken cache never takes the site down: it is disabled and logged.
Rate limits and counters
| Key | Type, default | Effect |
|---|---|---|
rateLimit.login.enabled |
bool, true |
Limits failed logins. |
rateLimit.login.maxAttempts |
int, 5 |
Attempts per client address and account together. |
rateLimit.login.ipMaxAttempts |
int, 20 |
Attempts per client address over all accounts. |
rateLimit.login.accountMaxAttempts |
int, 25 |
Attempts per account from all addresses (distributed guessing). |
rateLimit.login.window |
int seconds, 900 |
Length of the counting window. |
rateLimit.login.dir |
string, temp directory | Counter files. Set an absolute, writable path. |
counters.views.enabled |
bool, false |
Batches the article view counter instead of one database write per view. |
counters.views.hits |
int, 50 |
Hits per batch. |
counters.views.seconds |
int, 60 |
Longest delay of a batch. |
counters.views.dir |
string, temp directory | Counter files. Set an absolute, writable path. |
The rate limit uses the client address, so it only works correctly when http.trustedProxies is right behind a proxy.
Native analytics
| Key | Type, default | Effect |
|---|---|---|
analytics.enabled |
bool, false |
Collects identity-free daily aggregates (no cookies, no addresses). Needs the analytics tables (see the Deployment page). |
analytics.timezone |
string, UTC |
IANA time zone in which days are cut. Invalid values throw. |
analytics.retentionDays |
int, 62 |
How long daily rows are kept, 31 to 365. Run analytics:prune from cron daily. |
This is the privacy-friendly first-party measurement. It is independent of Google Analytics below: you can run either, both or none. A policy that fits most sites: native analytics on with retentionDays 62 (it sets no cookies and stores no addresses), Google Analytics off unless the operator has a legal basis and a consent process. Decide the legal side for your own site.
Google Analytics
| Key | Type, default | Effect |
|---|---|---|
googleAnalytics.measurementId |
string, empty | Measurement id such as G-XXXXXXXXXX. Empty, missing or malformed disables everything. |
The skeleton does not load Google's tag on its own. When the id is set, the layout renders the cookie banner and the footer link "Cookie settings", and public/cookie-consent.js loads gtag.js only after the visitor presses "Allow analytics". The choice is kept in the browser (localStorage), "Reject" removes the analytics cookies, and the tag is never loaded without consent. With the key empty the layout contains no banner, no button and no script. The text of the banner is in the locale catalogs (cookies.*); adapt it to your policy. A page cached before you set the id keeps its old markup until the cache is flushed.
"googleAnalytics": { "measurementId": "G-XXXXXXXXXX" }
Audit and change log
| Key | Type, default | Effect |
|---|---|---|
audit.retentionDays |
int, 365 |
How long cms_api_audit and cms_api_changes rows are kept, 30 to 3650. Invalid values throw. |
audit.revisionRetentionDays |
int, 365 |
How long cms_api_revisions (article draft history) are kept, 30 to 3650. |
Nothing is deleted automatically: run audit:prune from cron (--dry-run only counts, --days=N overrides both keys). The newest change of every resource and every revision a draft still uses are always kept. The tables need the change log columns (sh bin/update-database.sh DATABASE) when API v2 is enabled. What is recorded is described in Logging and audit.
Search engines
| Key | Type, default | Effect |
|---|---|---|
seo.allowIndexing |
bool, true |
false closes the whole site to crawlers, for a staging copy: robots.txt says Disallow: / and /sitemap.xml answers 404. |
seo.disallow |
list of paths, empty | Paths named in robots.txt, each starting with /. |
seo.sitemapExclude |
list of page links, empty | Pages left out of /sitemap.xml, each starting with /. |
/robots.txt and /sitemap.xml are generated for every request and registered in public/index.php. The sitemap lists the visible public pages once per language, with links to the other languages, and leaves out pages that need a login and pages with a hidden article. A language is listed only when the page has a translation. Pages that take parameters in the address are listed by their own address only.
Images and the API
| Key | Type, default | Effect |
|---|---|---|
apiV2.enabled |
bool, false (the skeleton sets true) |
Turns on the server-to-server API v2 used by the central Admin. Needs HTTPS and the API tables. |
apiV2.imageDirectory |
string, unset | Absolute directory for images uploaded through API v2. Unset means image upload is unavailable. Overridden by WBENGINE_IMAGE_DIR. Keep it outside public/, writable by PHP. Files are PNG, JPEG or WebP up to 512 KiB. |
Production example
A complete file for a site behind host Nginx, with every secret in the environment (WBENGINE_RECAPTCHA_SECRET) so the placeholders below are not real keys:
{
"timeZone": "Europe/Prague",
"debug": false,
"apiV2": {
"enabled": true,
"imageDirectory": "/var/lib/wbengine/images"
},
"renderer": {
"adapterName": "NativeRenderer",
"autoEscape": true,
"templatesDir": "/View/",
"compiledDir": "Compiled/",
"configDir": "/Config/",
"compileCheck": false
},
"dbAdapterDefinition": {
"adapterName": "Mysqli",
"hostname": "127.0.0.1",
"port": "3306",
"database": "wbengine_skeleton_app",
"username": "wbengine",
"password": "SET-A-LONG-RANDOM-PASSWORD",
"charset": "utf8mb4"
},
"recaptcha": { "secretKey": "" },
"http": {
"trustedProxies": ["127.0.0.1"],
"forwardedHeaders": ["x-forwarded-for", "x-forwarded-proto"]
},
"cache": {
"enabled": true,
"driver": "file",
"ttl": 300,
"dir": "/var/cache/wbengine",
"pages": {
"enabled": true,
"ttl": 60,
"denyPaths": ["/login/", "/profile/", "/integrations/"]
}
},
"i18n": {
"enabled": true,
"default": "en",
"locales": ["en", "cs"],
"detect": ["url"],
"baseUrl": "https://example.com"
},
"rateLimit": { "login": { "dir": "/var/lib/wbengine/ratelimit" } },
"counters": { "views": { "enabled": true, "dir": "/var/lib/wbengine/counters" } },
"analytics": { "enabled": true, "timezone": "Europe/Prague", "retentionDays": 62 },
"audit": { "retentionDays": 365, "revisionRetentionDays": 365 },
"googleAnalytics": { "measurementId": "" }
}
Where each part is deployed (permissions of the directories, proxies, cron) is described in Deployment, and the caching behavior in Caching.