~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)

FieldType / defaultConstraints & notes
engine.workersuint · 00–1024; 0 = the xrt default. Immutable across hot reload; changes are rejected
servicesarray · requiredmissing or non-array: field 'services' expect array, startup fails

Service fields (services[i], 13)

FieldType / defaultConstraints & notes
enabledbool · trueservice on/off switch
classstring · requiredwhitelist http / ws / tcp / udp / custom / app; anything else fails fast (app = an http node with a frontend, see the app node)
namestring · requiredglobally unique (duplicates report duplicate name); the key for xsServerFind
ipstring · 0.0.0.0listen address; the app class defaults to 127.0.0.1 (loopback binding does not trigger the Windows firewall popup)
portuint · —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
tlsbool · falseenables the TLS port; forbidden on udp (tls on udp)
port_tlsuint · 443startup is refused if it collides with the plaintext port on the same IP
ip_tlsstring · reuse ipTLS listen address
backloguint32 · —passed through to the xrt listen configuration; range-checked
recv_limitsize · —≤SIZE_MAX; ≤65535 on udp (recv limit out of range)
path / devlang / devfilestring · —service-level script defaults, synthesized into DefaultHost
dev_inc / dev_libstring · —script include / library search directories (semicolon-separated, relative to appPath)
all other fieldsanyenter 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 fieldtype / defaultconstraints & notes
titlestring · "xs app"window title
width / heightuint · 1280 / 800initial size in pixels; range 200–32767
min_width / min_heightuint · 0minimum size limit; 0 = unlimited
urlstring · "/"page to open when ready; a relative path is built with the actual port as http://<ip>:<PortBound>/…; absolute URLs allowed
devtoolsbool · falseenable the WebView2 developer tools
closestring · "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_fallbackstring · "ask"what to do when the Runtime is missing (values below); any failure falls back to headless
install_fallback valuemeaning
"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)

FieldType / defaultConstraints & notes
enabledbool · truehost on/off switch
namestring · requiredunique within the server, non-empty (duplicates / empty both fail fast)
hoststring · —semicolon-separated domain names; required non-empty for enabled http/ws vhosts
pathstring · —static asset root; a missing directory fails fast
devlangstring · —static pure static / c TCC scripts / application-defined
devfilestring · —script entry; a missing file fails compilation
tls_ca / tls_cert / tls_keystring · —PEM paths; host-level certificates → the data source of the SNI table
dev_inc / dev_libstring · —host-level script search paths
all other fieldsanyenter 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)

FieldType / defaultNotes
header_limituint · —request header cap (exceeding it → 431)
path_limituint · 2048path length cap (exceeding it → 414)
body_limituint · —request body cap (exceeding it → 413)
idle_timeoutuint · —idle timeout in ms (http/ws/tcp); the server closes on expiry
ws_message_limituint · —WS message cap; exceeding it closes with 1009
ws_protocolstring · —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)

FieldType / defaultConstraints & notes
static.headersobject · —≤8 fields; 8 reserved headers are banned (including Transfer-Encoding, Connection) — violations fail fast
static.indexarray · index.htmldirectory index candidates, tried in order with fallback
static.error_pagesobject · —keys must be 400–599 (out of range fails fast); values are error-page paths relative to path
static.deny_dotfilesbool · truedot-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.

FieldType / defaultConstraints and notes
enabledbool · falsemaster switch; false keeps the daemon off
account_key_filestring · —account key PEM (ES256); empty = each CA self-registers on first use. One key can open accounts at several CAs
emailstring · —ACME account contact email
dns_key_filestring · requiredDNS-01 credentials JSON: {"access_key_id":"…","access_key_secret":"…"} (alidns currently supported)
renew_before_daysuint · 301–89; renewal triggers when remaining lifetime falls below this many days
check_timestring · —fixed daily check time "HH:MM" (00:00–23:59); mutually exclusive with check_interval_hours
check_interval_hoursuint · 12interval mode (1–720); first check 5 minutes after startup, then on the interval
certs[]array · requiredcertificate 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)

FieldRequiredNotes
namenodisplay name; empty = the URL's host
urlyesACME directory URL; duplicate URLs fail fast
ca_filenotrust anchor PEM for this CA; empty = the system trust store
eab_kid_file / eab_hmac_filein pairfor 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.