~40 fields · constraints checked against src/core/config.h
xs.json Fields Reference
Configuration loading is fail-fast: any invalid field aborts startup, naming the exact field. Unlisted fields raise no error — they flow into Custom for the application to read (see xsConfigRoot in the xsapi reference).
Root fields (2)
Field
Type / default
Constraints & notes
engine.workers
uint · 0
0–1024; 0 = the xrt default. Immutable across hot reload; changes are rejected
services
array · required
missing or non-array: field 'services' expect array, startup fails
Service fields (services[i], 13)
Field
Type / default
Constraints & notes
enabled
bool · true
service on/off switch
class
string · required
whitelist http / ws / tcp / udp / custom / app; anything else fails fast (app = an http node with a frontend, see the app node)
name
string · required
globally unique (duplicates report duplicate name); the key for xsServerFind
ip
string · 0.0.0.0
listen address; the app class defaults to 127.0.0.1 (loopback binding does not trigger the Windows firewall popup)
port
uint · —
1–65535; required for non-custom (missing port); 0 = OS auto-assign, app class only (other classes report port 0 (auto) is only supported for class app); the actual port is read back via XS_ServerInfo.PortBound
tls
bool · false
enables the TLS port; forbidden on udp (tls on udp)
port_tls
uint · 443
startup is refused if it collides with the plaintext port on the same IP
ip_tls
string · reuse ip
TLS listen address
backlog
uint32 · —
passed through to the xrt listen configuration; range-checked
recv_limit
size · —
≤SIZE_MAX; ≤65535 on udp (recv limit out of range)
path / devlang / devfile
string · —
service-level script defaults, synthesized into DefaultHost
dev_inc / dev_lib
string · —
script include / library search directories (semicolon-separated, relative to appPath)
all other fields
any
enter server->Custom (the application reads them via XS_CustomReadUInt and friends)
The app node (class: "app" only, 9 window fields)
class: "app" is not a sixth protocol — it is an http node with a frontend: internally it takes the existing http assembly path verbatim (host_default / hosts / static / TLS / hot reload all mean the same), adding only a window descriptor and a frontend lifecycle — startup shows a loading window → once the service is ready window.url opens automatically → closing the window shuts down gracefully per the close semantics. The window is the display layer, the service is the substance: any window-thread failure degrades to a log line and never pushes the engine assembly or the shutdown chain onto the fail path.
window field
type / default
constraints & notes
title
string · "xs app"
window title
width / height
uint · 1280 / 800
initial size in pixels; range 200–32767
min_width / min_height
uint · 0
minimum size limit; 0 = unlimited
url
string · "/"
page to open when ready; a relative path is built with the actual port as http://<ip>:<PortBound>/…; absolute URLs allowed
devtools
bool · false
enable the WebView2 developer tools
close
string · "stop"
stop = closing the window gracefully stops the whole process; hide = hide the window and keep the service (a second launch wakes the existing window via single-instance detection; shutdown goes through the signal path)
install_fallback
string · "ask"
what to do when the Runtime is missing (values below); any failure falls back to headless
install_fallback value
meaning
"ask"
prompt whether to install the WebView2 Runtime (the default, user-facing form)
"silent"
download and install silently, unattended scenarios
"headless"
do not attempt installation, degrade straight to a headless service
port: 0 auto-assignment: allowed for the app class only (other classes fail fast); the kernel assigns atomically and the actual port is read back (no probe→close→rebind, avoiding the TOCTOU race). Hot reload is sticky per service name (stable across generations — open windows never notice); for stability across restarts configure a fixed port. The full platform / variant degradation matrix is on the webview reference page.
Host fields (host_default / hosts[i], 9)
Field
Type / default
Constraints & notes
enabled
bool · true
host on/off switch
name
string · required
unique within the server, non-empty (duplicates / empty both fail fast)
host
string · —
semicolon-separated domain names; required non-empty for enabled http/ws vhosts
path
string · —
static asset root; a missing directory fails fast
devlang
string · —
static pure static / c TCC scripts / application-defined
devfile
string · —
script entry; a missing file fails compilation
tls_ca / tls_cert / tls_key
string · —
PEM paths; host-level certificates → the data source of the SNI table
dev_inc / dev_lib
string · —
host-level script search paths
all other fields
any
enter host->Custom (the static.* knobs are listed below)
host_default always exists (synthesized from the service-level script fields when unconfigured); hosts[] is unlimited in count, and an unmatched vhost falls back to default.
Custom knobs (server level, read by the application)
Field
Type / default
Notes
header_limit
uint · —
request header cap (exceeding it → 431)
path_limit
uint · 2048
path length cap (exceeding it → 414)
body_limit
uint · —
request body cap (exceeding it → 413)
idle_timeout
uint · —
idle timeout in ms (http/ws/tcp); the server closes on expiry
ws_message_limit
uint · —
WS message cap; exceeding it closes with 1009
ws_protocol
string · —
forced subprotocol; validity-checked at startup, a type error fails fast
How they are read: the config lands in server->Custom (xvalue); the demo parses strictly non-negative via XS_CustomReadUInt.
Static-layer knobs (host-level Custom)
Field
Type / default
Constraints & notes
static.headers
object · —
≤8 fields; 8 reserved headers are banned (including Transfer-Encoding, Connection) — violations fail fast
static.index
array · index.html
directory index candidates, tried in order with fallback
static.error_pages
object · —
keys must be 400–599 (out of range fails fast); values are error-page paths relative to path
static.deny_dotfiles
bool · true
dot-prefixed files are always refused (including traversal attempts)
Path resolution rules
All file paths resolve to absolute paths against the configuration file's directory — the config directory is self-contained and can be moved as a whole
Run without arguments, XServer looks for xs.json in the executable's directory; an argument can specify it explicitly
Relative dev_inc paths resolve against appPath; mind the "decoy header" boundary from chapter 6
The acme node (certificate auto-renewal, requires the xacme extension)
A root-level acme node enables the built-in ACME daemon: a scheduled daily check, automatic DNS-01 renewal when the remaining lifetime drops below the threshold, and a hot swap (connections survive). All credentials live in standalone files (never in xs.json): account key, DNS credentials, EAB, trust anchors.
Field
Type / default
Constraints and notes
enabled
bool · false
master switch; false keeps the daemon off
account_key_file
string · —
account key PEM (ES256); empty = each CA self-registers on first use. One key can open accounts at several CAs
email
string · —
ACME account contact email
dns_key_file
string · required
DNS-01 credentials JSON: {"access_key_id":"…","access_key_secret":"…"} (alidns currently supported)
renew_before_days
uint · 30
1–89; renewal triggers when remaining lifetime falls below this many days
check_time
string · —
fixed daily check time "HH:MM" (00:00–23:59); mutually exclusive with check_interval_hours
check_interval_hours
uint · 12
interval mode (1–720); first check 5 minutes after startup, then on the interval
certs[]
array · required
certificate groups: {name, domains[], out_dir} — one group = one tls directory; renewal output uses the acme.sh-compatible layout (cert.pem=leaf / ca.pem=chain / key.pem / fullchain.pem)
directories[] (the CA fallback chain)
Field
Required
Notes
name
no
display name; empty = the URL's host
url
yes
ACME directory URL; duplicate URLs fail fast
ca_file
no
trust anchor PEM for this CA; empty = the system trust store
eab_kid_file / eab_hmac_file
in pair
for CAs requiring EAB registration (e.g. litessl); supplying only one fails fast
Compatibility: the single field directory (plus a root-level ca_file) equals a one-element chain; present together with directories it errors. Runtime semantics: first success wins — CAs are tried in order and a failure falls through to the next; with a valid local certificate no CA is contacted at all; renewal retries from the head of the chain (automatic return to the primary once it recovers). The daemon thread owns its network engine and snapshots the config at startup (restart to apply).
Renewal decisions read the certificate inside acme-store/ (not the host tls directory); a forced re-issue = delete acme-store/certs/<primary domain>/ and restart (first check after 5 minutes). Variants without xacme: a present config prints a notice and is ignored; the daemon stays inert.