Handler Reference

Handler type hierarchy for Elide server routes.

Every Route has exactly one Handler, and each ServerBlock has a default handler for unmatched requests. Handlers are open classes that form a discriminated union via typealias Handler. Pick the concrete subclass that matches the behavior you need:

StaticFiles — serve files from a local directory ReverseProxy — forward requests to one or more upstream servers Redirect — issue an HTTP redirect response Cgi — execute a program per request via CGI (RFC 3875) Respond — emit a fixed inline response (useful for health checks, maintenance pages, and catch-all error routes)

Handlers can also be set directly on a ServerBlock.handler using a bare status code integer (e.g., 404) as a shorthand for a Respond with that status.

> This page is auto-generated from the PKL schema. See the guide for usage examples.

Types

HttpStatus

HTTP status code (100–599 inclusive).

pkl
typealias HttpStatus = UInt(isBetween(100, 599))

CompressionAlgo

Compression algorithm for response bodies. Listed in typical priority order (best ratio first):

  • "zstd" — Zstandard; best ratio and speed for most workloads
  • "br" — Brotli; excellent for text/HTML but slower to compress
  • "gzip" — universal client support; lowest ratio of the three
pkl
typealias CompressionAlgo = "gzip" | "br" | "zstd"

LoadBalancingPolicy

Load-balancing policy for distributing requests across a ReverseProxy upstream pool.

  • "round_robin" — cycle through upstreams sequentially (respects
Upstream.weight for weighted distribution)
  • "least_connections" — send to the upstream with fewest active
connections
  • "random" — uniform random selection
  • "ip_hash" — hash the client IP for sticky sessions
  • "first" — always send to the first healthy upstream
  • "cookie" — sticky sessions via a server-set cookie
  • "consistent_hash" — consistent hashing on request URI; minimizes
redistribution when upstreams are added or removed
pkl
typealias LoadBalancingPolicy = "round_robin" | "least_connections" | "random" | "ip_hash" | "first" | "cookie" | "consistent_hash"

RedirectStatus

Valid HTTP status codes for redirect responses.

  • 301 Moved Permanently — cacheable, method may change to GET
  • 302 Found — not cached by default, method may change to GET
  • 307 Temporary Redirect — preserves request method and body
  • 308 Permanent Redirect — cacheable, preserves method and body
pkl
typealias RedirectStatus = UInt(this == 301 || this == 302 || this == 307 || this == 308)

CacheControlPreset

Cache-Control header value for static asset responses. Common presets are offered as literal options, but any valid Cache-Control string is accepted:
  • "no-store" — never cache (sensitive data)
  • "no-cache" — cache but always revalidate
  • "private" — cache in the browser only, not CDN/proxy
  • "public, max-age=3600" — cache 1 hour
  • "public, max-age=86400" — cache 1 day
  • "public, max-age=31536000, immutable" — cache 1 year, never
revalidate (fingerprinted assets)

Custom example:

pkl
cacheControl = "public, max-age=600, stale-while-revalidate=60"
pkl
typealias CacheControlPreset = "no-store" | "no-cache" | "private" | "public, max-age=3600" | "public, max-age=86400" | "public, max-age=31536000, immutable" | String

Handler

Union of all valid handler types. Use one of: StaticFiles, ReverseProxy, Redirect, Cgi, or Respond.

pkl
typealias Handler = StaticFiles | ReverseProxy | Redirect | Cgi | Respond

---

HeaderOps

Header mutation operations applied to a proxied request or response.

Mutations are applied in order: set, then add, then remove. Used by ReverseProxy.requestHeaders and ReverseProxy.responseHeaders.

pkl
requestHeaders {
  set { ["X-Forwarded-Proto"] = "https" }
  remove { "Cookie" }
}
FieldTypeDefaultDescription
setMapping(empty)Headers to set unconditionally. Overwrites any existing value with
addMapping(empty)Headers to append. Adds a new header value without removing any
removeListing(empty)Header names to remove entirely from the request or response.

set

Headers to set unconditionally. Overwrites any existing value with the same name.

add

Headers to append. Adds a new header value without removing any existing values for the same name.

---

Upstream

A single upstream server in a ReverseProxy pool.

In simple cases, a bare string (e.g., "backend:8080") can be used instead of a full Upstream object. Use the object form when you need to set weight, connection limits, or backup status.
FieldTypeDefaultDescription
addressString(!isEmpty)(required)Upstream address. Accepts host:port or a full URL:
weightUInt(isPositive)1Relative weight for weighted load balancing. Higher values receive
maxConnectionsUInt0Maximum simultaneous connections to this upstream. 0 (default)
backupBooleanfalseMark this upstream as a backup. Backup upstreams only receive traffic

address

Upstream address. Accepts host:port or a full URL:

pkl
`"backend-1:8080"`
`"http:<<>>
`"https://internal.example.com"`

When a scheme is included, TLS is used for https:// targets.

weight

Relative weight for weighted load balancing. Higher values receive proportionally more traffic. Only meaningful with policies that support weights (e.g., "round_robin").

maxConnections

Maximum simultaneous connections to this upstream. 0 (default) means unlimited. Set this to protect upstreams from overload.

backup

Mark this upstream as a backup. Backup upstreams only receive traffic when all primary (non-backup) upstreams are unhealthy.

---

HealthCheck

Active and passive health-check configuration for ReverseProxy upstream pools.

Active checks send periodic probe requests to each upstream. Passive checks observe real traffic and mark an upstream unhealthy when proxied requests fail. Both can run simultaneously.

pkl
healthCheck { path = "/healthz"; interval = 5.s }
FieldTypeDefaultDescription
pathString"/health"HTTP path probed by the active health checker. The probe issues a
intervalDuration10.sInterval between active health-check probes per upstream.
timeoutDuration5.sMaximum time to wait for a probe response before treating it as a
unhealthyThresholdUInt(isPositive)3Consecutive probe failures required to mark an upstream unhealthy.
healthyThresholdUInt(isPositive)2Consecutive probe successes required to restore an upstream to
passiveBooleantrueEnable passive health checks. When true, an upstream is marked
circuitBreakerThresholdUInt?nullNumber of passive failures within the observation window that trips

path

HTTP path probed by the active health checker. The probe issues a GET request to this path; a 2xx response is considered healthy.

timeout

Maximum time to wait for a probe response before treating it as a failure.

unhealthyThreshold

Consecutive probe failures required to mark an upstream unhealthy. A higher value makes the health checker more tolerant of transient errors but slower to detect real failures.

healthyThreshold

Consecutive probe successes required to restore an upstream to healthy status after it has been marked unhealthy.

passive

Enable passive health checks. When true, an upstream is marked unhealthy after a proxied request fails without receiving any response (connection refused, timeout, etc.). Passive checks react faster than active probes but can be triggered by transient network issues.

circuitBreakerThreshold

Number of passive failures within the observation window that trips the circuit breaker, stopping all traffic to the upstream until the next active probe succeeds. null (default) disables the circuit breaker.

---

StaticFiles

Open class — can be extended.

Serves files from a local directory.

Static assets are served with ETag and Last-Modified headers for conditional request support. When preCompress is enabled (the default), Elide pre-compresses every file at startup and serves the compressed variant directly, eliminating per-request compression overhead.

pkl
handler = new StaticFiles { root = "./dist"; spaFallback = true }
FieldTypeDefaultDescription
typeString"static" (fixed)(fixed)
rootString(!isEmpty)(required)Root directory from which files are resolved. Paths are resolved
indexString"index.html"Primary index file returned when a bare directory path is requested.
spaFallbackBooleanfalseServe index for any path that does not resolve to an existing file.
preCompressBooleantruePre-compress all files at startup and cache the compressed variants
compressionListingnew { "zstd"; "br"; "gzip" }Compression algorithms to offer. When the client Accept-Encoding
etagBooleantrueEmit ETag headers for conditional request support (If-None-Match).
rangeRequestsBooleantrueAccept Range request headers for partial-content responses
cacheControlCacheControlPreset?nullCache-Control header value sent with every file response. null
tryFilesListing(empty)Ordered list of file paths to try for each request (similar to nginx
indexFilesListingnew { "index.html"; "index.htm" }Index files to look for when a directory path is requested, tried in
directoryListingBooleanfalseEnable auto-generated HTML directory listings for paths that resolve
directoryTemplateString?nullCustom HTML template for directory listing pages. null uses the
autoindexSortString"name_asc"Sort order for directory listings. Accepted values:

root

Root directory from which files are resolved. Paths are resolved relative to the working directory of the elide serve process.

pkl
root = "./public"
root = "/var/www/html"

index

Primary index file returned when a bare directory path is requested. This value is also used as the SPA fallback target when spaFallback = true.

spaFallback

Serve index for any path that does not resolve to an existing file. Required for single-page applications that handle client-side routing (e.g., React Router, Vue Router). The response carries the original request URL so the client-side router can match it.

preCompress

Pre-compress all files at startup and cache the compressed variants in memory. Highly recommended for production — eliminates per-request compression overhead at the cost of increased startup time and memory.

compression

Compression algorithms to offer. When the client Accept-Encoding header supports multiple algorithms, the first match in this list wins. Order from best ratio to widest support.

etag

Emit ETag headers for conditional request support (If-None-Match). ETags are computed from file content hashes, enabling 304 Not Modified responses that save bandwidth.

rangeRequests

Accept Range request headers for partial-content responses (206 Partial Content). Required for resumable downloads and media seeking.

cacheControl

Cache-Control header value sent with every file response. null (default) omits the header, leaving caching behavior to the client or downstream CDN. See CacheControlPreset for common values.

tryFiles

Ordered list of file paths to try for each request (similar to nginx try_files). The first path that resolves to an existing file is served. An empty list (default) disables this behavior.

indexFiles

Index files to look for when a directory path is requested, tried in listed order. The first file found is served.

directoryListing

Enable auto-generated HTML directory listings for paths that resolve to a directory without a matching index file. When false (default), such requests return 404.

directoryTemplate

Custom HTML template for directory listing pages. null uses the built-in template. Ignored when directoryListing = false. The template receives listing data as template variables.

autoindexSort

Sort order for directory listings. Accepted values: "name_asc", "name_desc", "size_asc", "size_desc", "mtime_asc", "mtime_desc".

---

ReverseProxy

Open class — can be extended.

Forwards requests to one or more upstream servers with load balancing, health checking, and optional response caching.

pkl
handler = new ReverseProxy {
  upstreams { "backend-a:8080"; "backend-b:8080" }
  loadBalancing = "least_connections"
  healthCheck {}
}
FieldTypeDefaultDescription
typeString"proxy" (fixed)(fixed)
upstreamsListingUpstream>(!isEmpty)(required)Upstream server pool. At least one upstream is required. Accepts bare
stripPrefixBooleanfalseStrip the matched route prefix before forwarding to the upstream.
loadBalancingLoadBalancingPolicy"round_robin"Load-balancing policy for distributing requests across upstreams.
healthCheckHealthCheck?nullActive and passive health-check configuration. null (default)
timeoutDuration30.sMaximum time to wait for an upstream to begin sending response
responseTimeoutDuration5.minMaximum time for the upstream to send the complete response body
bufferResponseBooleanfalseBuffer the full upstream response in memory before sending it to the
requestHeadersHeaderOps(empty)Header mutations applied to the outgoing request sent to the
responseHeadersHeaderOps(empty)Header mutations applied to the upstream's response before it is
websocketBooleantrueEnable WebSocket proxying via HTTP Upgrade handling. When true
preserveHostBooleantruePreserve the original client Host header when forwarding to the
cacheProxyCache?nullResponse cache for this reverse proxy. When set, cacheable upstream

upstreams

Upstream server pool. At least one upstream is required. Accepts bare strings ("host:port") or full Upstream objects for weight and connection-limit control.

pkl
upstreams { "backend:8080" }
upstreams { new Upstream { address = "backend:8080"; weight = 2 } }

stripPrefix

Strip the matched route prefix before forwarding to the upstream. For example, a route matching "/api/**" with stripPrefix = true forwards /api/users as /users.

loadBalancing

Load-balancing policy for distributing requests across upstreams. See LoadBalancingPolicy for all options.

healthCheck

Active and passive health-check configuration. null (default) disables health checks entirely — all upstreams are presumed healthy. See HealthCheck for probe path, intervals, and circuit-breaker options.

timeout

Maximum time to wait for an upstream to begin sending response headers. If this timeout expires, the request fails with 504 Gateway Timeout.

responseTimeout

Maximum time for the upstream to send the complete response body after headers have been received. Protects against slow or stalled upstream responses.

bufferResponse

Buffer the full upstream response in memory before sending it to the client. Enables response body inspection and rewriting via middleware, but increases memory usage proportional to response size.

requestHeaders

Header mutations applied to the outgoing request sent to the upstream. See HeaderOps for set/add/remove operations.

responseHeaders

Header mutations applied to the upstream's response before it is forwarded to the client. See HeaderOps for set/add/remove operations.

websocket

Enable WebSocket proxying via HTTP Upgrade handling. When true (default), Upgrade: websocket requests are forwarded to the upstream and the connection is held open bidirectionally.

preserveHost

Preserve the original client Host header when forwarding to the upstream. When false, the Host header is rewritten to the upstream's address. Most upstreams expect the original host; set to false only when the upstream performs its own virtual-host routing.

cache

Response cache for this reverse proxy. When set, cacheable upstream responses are stored and served from a two-tier (memory + disk) cache. null (default) disables caching.

See ProxyCache for zone, size limit, and coalescing options.

pkl
cache = new ProxyCache {}
cache = new ProxyCache { lock = true }

---

Redirect

Open class — can be extended.

Issues an HTTP redirect to a target URL.

pkl
handler = new Redirect {
  target = "https://new.example.com{path}"
  status = 301
}
FieldTypeDefaultDescription
typeString"redirect" (fixed)(fixed)
targetUri(required)Destination URL for the redirect. Supports template variables that
statusRedirectStatus308HTTP status code for the redirect response. The default (308)

target

Destination URL for the redirect. Supports template variables that are expanded from the original request:

{path} — the matched request path (e.g., /old/page) {query} — the raw query string (e.g., ?foo=bar)

Example: "https://new.example.com{path}{query}"

status

HTTP status code for the redirect response. The default (308) issues a permanent redirect that preserves the request method. See RedirectStatus for all options.

---

Cgi

Open class — can be extended.

Executes a program per request using the CGI (Common Gateway Interface) protocol (RFC 3875).

The program receives request metadata via environment variables (REQUEST_METHOD, PATH_INFO, QUERY_STRING, etc.), reads the request body from stdin, and writes response headers + body to stdout.

This handler can run any executable: shell scripts, Python, Ruby, PHP, compiled binaries, or Elide JS/TS scripts (via the interpreter field).

pkl
handler = new Cgi {
  script = "./cgi-bin/report.py"
  interpreter = "/usr/bin/python3"
  timeout = 60.s
}
FieldTypeDefaultDescription
typeString"cgi" (fixed)(fixed)
scriptString(!isEmpty)(required)Path to the CGI script or executable. Resolved relative to
interpreterString?nullInterpreter used to execute the script (e.g., "/usr/bin/python3",
envMapping(empty)Extra environment variables passed to the CGI process, in addition
timeoutDuration30.sMaximum execution time for the CGI process. If the process is still
workingDirString?nullWorking directory for the CGI process. When null (default), the

script

Path to the CGI script or executable. Resolved relative to workingDir (or the server's working directory if workingDir is null).

interpreter

Interpreter used to execute the script (e.g., "/usr/bin/python3", "/usr/bin/env ruby"). When null, the script is executed directly and must be executable or have a shebang (#!/...) line.

env

Extra environment variables passed to the CGI process, in addition to the standard CGI variables. Useful for passing configuration or secrets without command-line arguments.

timeout

Maximum execution time for the CGI process. If the process is still running after this duration, it is killed with SIGKILL and the client receives a 504 Gateway Timeout response.

workingDir

Working directory for the CGI process. When null (default), the server's own working directory is used.

---

Respond

Open class — can be extended.

Emits a fixed inline HTTP response without invoking any upstream or reading from disk. Useful for health-check endpoints, maintenance pages, stub APIs, and catch-all error routes.

pkl
handler = new Respond {
  status = 503
  contentType = "text/html; charset=utf-8"
  body = "<h1>Down for maintenance</h1>"
  headers { ["Retry-After"] = "3600" }
}
FieldTypeDefaultDescription
typeString"respond" (fixed)(fixed)
statusHttpStatus200HTTP status code for the response.
bodyString?nullResponse body text. null sends an empty body (content-length 0).
contentTypeString"text/plain; charset=utf-8"Value of the Content-Type response header.
headersMapping(empty)Additional response headers. These are sent alongside the standard

headers

Additional response headers. These are sent alongside the standard headers (Content-Type, Content-Length).

---

ProxyCache

Open class — can be extended.

Response cache for a ReverseProxy handler.

Cacheable upstream responses are stored in a two-tier cache (memory hot tier + disk cold tier). Subsequent requests for the same resource are served from cache without contacting the upstream.

All fields have sensible defaults — cache = new ProxyCache {} enables caching that works for most workloads. Cache keys are derived from the request method, URI, and Vary-selected headers.
FieldTypeDefaultDescription
zoneString"default"Cache zone name. Used as the shard subdirectory on disk under path.
pathString"/var/cache/elide/proxy"Base directory for on-disk cache files. Must be writable by the
maxMemoryDataSize64.mbMaximum size of the in-memory hot-tier cache. Frequently accessed
maxDiskDataSize512.mbMaximum size of the on-disk cold-tier cache. Entries evicted from
maxBodySizeDataSize10.mbMaximum individual response body size to cache. Responses larger
maxTtlDuration0.sMaximum TTL for cached entries, regardless of upstream
forceCacheBooleanfalseCache responses even when they lack explicit Cache-Control or
lockBooleanfalseEnable request coalescing (cache lock) to prevent thundering-herd
lockTimeoutDuration5.sMaximum time a coalesced request will wait for the first request to

zone

Cache zone name. Used as the shard subdirectory on disk under path. Use distinct zone names when multiple ReverseProxy handlers share the same path directory.

path

Base directory for on-disk cache files. Must be writable by the server process.

maxMemory

Maximum size of the in-memory hot-tier cache. Frequently accessed entries are promoted here for sub-millisecond serving.

maxDisk

Maximum size of the on-disk cold-tier cache. Entries evicted from memory are written to disk. When this limit is reached, the least recently used entries are evicted.

maxBodySize

Maximum individual response body size to cache. Responses larger than this threshold are streamed directly to the client and bypass the cache entirely.

maxTtl

Maximum TTL for cached entries, regardless of upstream Cache-Control directives. 0.s (default) means the cache honors the upstream's TTL without imposing a cap.

forceCache

Cache responses even when they lack explicit Cache-Control or Expires headers. Such responses are cached with a default TTL of 60 seconds. Useful for upstreams that omit cache headers but serve cacheable content.

lock

Enable request coalescing (cache lock) to prevent thundering-herd stampedes. When enabled, concurrent requests for the same uncached resource are collapsed — only the first request is forwarded upstream and the remaining callers wait for its result.

lockTimeout

Maximum time a coalesced request will wait for the first request to complete. If the lock is not released within this duration, the waiting request is forwarded upstream independently. Only effective when lock = true.

---