<channel>
Real-time multi-client state. Declared cells auto-sync across connected clients over WebSocket. V5-strict body; the compiler generates the endpoint, the wire protocol, and the reconnect logic.
Syntax
<channel name="chat" topic="lobby">
<cell> = initialValue
...
</channel>
name=
is the channel identifier (drives the WS endpoint
/_scrml_ws/<name>).
topic=
optionally partitions clients (defaults to
name).
Inside the body, V5-strict declarations
(<cell> = init)
create channel-scoped reactive cells that auto-sync
across subscribed clients.
Worked example
A minimal chat room. One channel, one synced list, one
publisher function. The cell write runs on the CLIENT and
syncs to every subscriber automatically (§38.4) — it needs no
server
keyword, and a server-escalated function may not read a
channel cell at all.
<program>
<channel name="chat" topic="lobby">
<messages> = []
${ function postMessage(author, body) {
@messages = [...@messages, { author, body, ts: Date.now() }]
} }
</channel>
${ const count = @messages.length }
</program>
<messages>
is declared inside the channel body, so it auto-syncs.
Anywhere else in the
<program>,
@messages
reads it — program-scope visibility for channel
cells is the canonical pattern (per §38.4 + Insight
30).
Semantics
- Children of <program>, not file-level siblings. Channels live INSIDE the entry-file <program> (sibling of <page>). Pure channel-module files (no <program> in the file) MAY declare channels at file top — the cross-file inline-expansion pattern per §38.12.6. Per Insight 30 (S87 placement reversal).
- No @shared modifier. Auto-sync is a consequence of being declared inside the channel body, NOT a per-cell modifier. E-CHANNEL-SHARED-MODIFIER fires on the legacy form. Per §38.4.
-
V5-strict body.
<cell> = initdeclares;@cellreads / writes. Same access discipline as everywhere else in scrml. -
Auto-injected helpers in server functions.
Inside a channel body, server functions get
broadcast(data)anddisconnect()injected into scope. Usebroadcastfor one-shot messages that don't fit a reactive cell; use cell writes for state every client should track. -
Event handlers.
onserver:message=handler(msg)andonclient:connect=handler()handle the underlying WS events. Handler params (msg) are function-local locals, accessed bare. V5-strict local semantics; not state. -
Compiler generates the wire. WebSocket
endpoint at
/_scrml_ws/<name>; reconnect logic (channel-reconnect=Non <program> sets the project-level default); payload serialization; per-topic broadcast graph. The developer writes the declared cells; the rest is emitted.
Errors this feature can fire
- E-CHANNEL-OUTSIDE-PROGRAM — channel at file top in a file that already has <program>
- E-CHANNEL-INSIDE-PAGE — channel nested inside a <page>
- E-CHANNEL-SHARED-MODIFIER — legacy @shared modifier (removed v0.next)
- E-CHANNEL-008 — channel name collision
Edge cases
-
Pure channel-module files. A
.scrmlfile with no top-level <program> may declare channels at file top. Other entry files import the channel as a unit; the compiler inlines the channel's cells into the importer's <program> (the "channel-module sharing pattern", §38.12.6). -
topic= is the partition key.
Multiple clients on the same channel
name=but differenttopic=are isolated. The default istopic=name— one global room. For per-document rooms, derivetopic=from the current route. -
Channel writes from clients are server-
mediated. A client can only write a channel
cell via a server function declared inside the channel
body. The wire protocol passes through the
compiler-generated server-fn endpoint, which writes
the cell and broadcasts to peers. Direct client-side
@cell = ...writes are scoped to the local client. -
Channel-reconnect default. Project-
level reconnect window via
<program channel-reconnect=N>(S81 §38.3.1). Set to zero to disable auto-reconnect on transient WS drops.
Related features
-
<program>
— channels live as children of the
entry-file <program>.
channel-reconnect=Non <program> sets the default reconnect window. -
server function
— inside a channel body, server fns get
broadcastanddisconnectinjected. Reference page queued. - server function* (SSE generators, §37) — the one-way server-to-client equivalent; lighter than a channel when clients don't write back.
Availability
| Surface | Since | Notes |
|---|---|---|
| V5-strict body / no @shared | D3 rewrite (2026-05-04) | Major SPEC §38 rewrite — @shared removed; auto-sync from placement |
| Channels as <program> children | Insight 30 (S87) | Placement reversal — channels inside <program>, not file-level |
| channel-reconnect=N on <program> | S81 (§38.3.1) | Project-level reconnect window default |
| Cross-file channel-module inline expansion | §38.12.6 | Pure-channel files import as a unit |
Specification
This page summarizes SPEC §38 (WebSocket Channels). The normative text lives at compiler/SPEC.md in the scrmlTS repository.
Quick-lookup anchors: SPEC-INDEX.md entries — channel file-level placement, channel V5-strict body, v1→v0.next channel migration.