inject.ctx
inject.ctx defines runtime context fields and helper APIs available to inject scripts.
This page describes interface-level specifications only.
Applicable Phases
browser: script runs in browser runtime.request: script runs before upstream forwarding.response: script runs after upstream response.
Common Fields (All Phases)
| Field | Type | Description |
|---|---|---|
ctx.id | string | Current inject id |
ctx.src | string | Current script source (src) |
ctx.phase | string | Current phase: browser/request/response |
ctx.params | object | Resolved script params (after $persist) |
ctx.safe_uid | string | Platform user ID (SAFE_UID) |
ctx.request.host | string | Request host |
ctx.request.path | string | Request path |
ctx.request.raw_query | string | Raw query without ? |
Notes:
- When
auth_required=falseand no valid login exists,ctx.safe_uidcan be empty string.
ctx.params Resolution Rules
Source:
ctx.paramscomes frominject.do[].params.- Final result is always an object.
Static values:
- Values without
$persistmarker are passed through as-is.
Dynamic values ($persist):
- Marker forms:
{ $persist: "<key>" }or{ $persist: "<key>", default: <any> } - Dynamic resolution applies only when marker is explicitly used.
- If persist key exists, use persisted value.
- If key missing and
defaultexists, use default. - If key missing and no
default, usenull.
Resolution timing:
browser: re-resolved on each runtime trigger (page load, route/hash changes, etc.).request/response: re-resolved before each script execution.
Fallback:
- If resolved params is not an object,
ctx.paramsbecomes{}.
ctx.request Field Semantics
Common fields:
| Field | Type | Description |
|---|---|---|
ctx.request.host | string | Host without scheme |
ctx.request.path | string | Path (starts with /) |
ctx.request.raw_query | string | Raw query without ? |
Phase-specific fields:
| Field | Phase | Type | Description |
|---|---|---|---|
ctx.request.hash | browser | string | URL hash without # |
ctx.request.method | request/response | string | HTTP method in uppercase |
ctx.runtime Fields (browser)
| Field | Type | Description |
|---|---|---|
ctx.runtime.executedBefore | bool | Whether executed before in current page lifecycle |
ctx.runtime.executionCount | int | Execution count in current page lifecycle (starts from 1) |
ctx.runtime.trigger | string | Trigger source (for example load, hashchange) |
ctx.status Field
| Field | Phase | Type | Description |
|---|---|---|---|
ctx.status | response | int | Current response status code |
Helper Matrix
| Helper | browser | request | response |
|---|---|---|---|
ctx.base64 | Yes | Yes | Yes |
ctx.persist | Yes | Yes | Yes |
ctx.headers | No | Yes | Yes |
ctx.body | No | Yes | Yes |
ctx.flow | No | Yes | Yes |
ctx.fs | No | Yes | Yes |
ctx.client | No | Yes | Yes |
ctx.dev | No | Yes | Yes |
ctx.net | No | Yes | Yes |
ctx.dump | No | Yes | Yes |
ctx.response | No | Yes | Yes |
ctx.proxy | No | Yes | Yes |
ctx.base64
ctx.base64.encode(text) -> stringctx.base64.decode(text) -> string
ctx.persist
Persisted key/value storage isolated by SAFE_UID.
request/response:
ctx.persist.get(key) -> anyctx.persist.set(key, value) -> voidctx.persist.del(key) -> voidctx.persist.list(prefix?) -> Array<{key: string, value: any}>
browser (async):
ctx.persist.get(key) -> Promise<any | undefined>ctx.persist.set(key, value) -> Promise<void>ctx.persist.del(key) -> Promise<void>ctx.persist.list(prefix?) -> Promise<Array<{key: string, value: any}>>
Constraints:
listreturns full results sorted by key (ascending).ctx.safe_uidmust be non-empty to accessctx.persist.key/prefixare trimmed. Emptykeycannot be used forget/set/del.- Values passed to
setmust be JSON-serializable. - No extra app-layer encryption is provided.
ctx.headers (request/response)
ctx.headers.get(name) -> stringctx.headers.getValues(name) -> string[]ctx.headers.getAll() -> Record<string, string[]>ctx.headers.set(name, value) -> voidctx.headers.add(name, value) -> voidctx.headers.del(name) -> void
Notes:
ctx.headers.set(name, null)orctx.headers.set(name, undefined)deletes the header.ctx.headers.set(name, array)deletes existing values first, then adds each array item as a separate header value.ctx.headers.add(name, value)appends one stringified value.null/undefinedis ignored.
ctx.body (request/response)
ctx.body.getText(opts?) -> stringctx.body.getJSON(opts?) -> anyctx.body.getForm(opts?) -> Record<string, string[]>ctx.body.set(body, opts?) -> void
opts:
| Field | Type | Default | Description |
|---|---|---|---|
max_bytes | int | 1048576 | Max bytes for get* read |
content_type | string | empty | Override Content-Type on set |
Notes:
ctx.body.set(...)updatesContent-Lengthand clearsContent-EncodingandETag.- In
ctx.body.set(body, ...), string values are written as-is,null/undefinedwrites an empty body, and other values are JSON-encoded before writing.
ctx.flow (request/response)
ctx.flow.get(key) -> anyctx.flow.set(key, value) -> voidctx.flow.del(key) -> voidctx.flow.list(prefix?) -> Array<{key: string, value: any}>
Constraints:
key/prefixare trimmed. Emptykeycannot be used forget/set/del.- Values passed to
setmust be JSON-serializable. listreturns full results sorted by key (ascending).
ctx.fs (request/response)
ctx.fs.exists(path) -> boolctx.fs.readText(path, opts?) -> stringctx.fs.readJSON(path, opts?) -> anyctx.fs.stat(path) -> objectctx.fs.list(path) -> string[]
Parameter constraints:
pathmust be absolute.ctx.fs.readText(...)andctx.fs.readJSON(...)read at mostmax_bytesbytes. If the file exceeds the limit, an error is thrown.ctx.fs.readJSON(...)parses file content as JSON and returns the decoded value.ctx.fs.list(...)returns direct child names only, without parent paths, sorted by name in ascending order.
opts:
| Field | Type | Default | Description |
|---|---|---|---|
max_bytes | int | 1048576 | Max bytes for readText/readJSON file read |
ctx.fs.stat(path) returns:
| Field | Type | Description |
|---|---|---|
is_file | bool | Whether the path is a regular file |
is_dir | bool | Whether the path is a directory |
size | int | File size in bytes |
mod_time_unix | int | Modification time as a Unix timestamp in seconds |
mode | int | File mode value |
ctx.client (request/response)
ctx.client.id -> stringctx.client.idcomes from the current client identity injected by ingress. It may be empty when no client context is attached.
ctx.dev (request/response)
ctx.dev.id -> stringctx.dev.online() -> bool
Notes:
ctx.dev.idis currently read from/lzcapp/var/_lzc_ext/dev.id.ctx.dev.online()reads cached status only. The cache is refreshed in background by lzcinit for the current request UID.
ctx.net (request/response)
ctx.net.joinHost(host, port) -> stringctx.net.via.local() -> objectctx.net.via.host() -> objectctx.net.via.client(id) -> objectctx.net.reachable(protocol, host, port, via?) -> bool
Notes:
protocolcurrently supportstcp,tcp4, andtcp6.hostaccepts either a container-reachable hostname or an IP literal.ctx.net.via.local()returns{ type: "local" }, meaning the current container network.ctx.net.via.host()means accessing the lzcos host network through remotesocket.ctx.net.via.client(id)means accessing a specific client node network through remotesocket.reachable(...)performs a live network probe with a default timeout of about1200ms.viais optional; when omitted, the current container network is used.
ctx.dump (request/response)
ctx.dump.request(opts?) -> stringctx.dump.response(opts?) -> string
opts:
| Field | Type | Default | Description |
|---|---|---|---|
include_body | bool | false | Include body text |
max_body_bytes | int | 4096 | Max bytes for dumped body |
ctx.response (request/response)
ctx.response.send(status, body?, opts?) -> void
opts:
| Field | Type | Default | Description |
|---|---|---|---|
headers | object | empty | Additional response headers |
content_type | string | text/html; charset=utf-8 | Content-Type override |
location | string | empty | Redirect location (required for 301/302/303/307/308) |
Notes:
headersvalues can be single values or arrays. Arrays add multiple values for the same header.- Header fields with
nullvalues are not written.
ctx.proxy (request/response)
ctx.proxy.to(url, opts?) -> void
opts:
| Field | Type | Default | Description |
|---|---|---|---|
use_target_host | bool | false | Rewrite Host to target host |
timeout_ms | int | 5000 | Per-request proxy timeout in milliseconds |
path | string | empty | Optional path rewrite |
query | string | empty | Optional query rewrite without ? |
via | object | empty | Optional network path object, usually from ctx.net.via.local(), ctx.net.via.host(), or ctx.net.via.client(id) |
on_fail | string | keep_original | Failure policy: keep_original or error |
Notes:
urlmust include scheme and host.- If
pathis not set, the target URL path is used first. If the target URL has no path, the original request path is kept. - If
queryis not set, the target URL query is used first. If the target URL has no query, the original request query is kept.
Execution Model Constraints
request/responsephases are synchronous (noPromise/asyncsupport).browsercan use async APIs (for examplectx.persistPromise methods).