Reference
The canonical function-level API reference is the generated Gleam documentation hosted on HexDocs:
This page provides a module map, broadcast cheatsheet, Phoenix wire protocol reference, and client compatibility notes.
Module map
Section titled “Module map”| Module | What it does | When to use it |
|---|---|---|
beryl | Top-level API: start the registry, register channels, broadcast | Entry point for all applications |
beryl/channel | Channel builder, callback types, HandleResult | Defining channel behaviour |
beryl/coordinator | OTP actor that owns a single socket/topic pair | Rarely needed directly |
beryl/socket | Socket abstraction, assigns helpers | Inside channel callbacks |
beryl/topic | Topic parsing, wildcard matching, segment extraction | Dynamic routing, multi-tenant patterns |
beryl/pubsub | Distributed PubSub backed by Erlang pg | Multi-node fan-out, cluster broadcasts |
beryl/presence | OTP actor wrapping the presence CRDT | Tracking who is online |
beryl/presence/state | Pure add-wins observed-remove CRDT | Testing, custom merge logic |
beryl/group | Named sets of topics for bulk broadcast | Rooms with multiple sub-topics |
beryl/wire | JSON encode/decode for the Phoenix wire format | Custom transports, protocol debugging |
beryl/transport/mist | Mist WebSocket upgrade and dispatch | Wiring beryl to an HTTP server |
Broadcast / push / send cheatsheet
Section titled “Broadcast / push / send cheatsheet”| Goal | API | Notes |
|---|---|---|
| Reply to an incoming message | channel.Reply(event, payload, socket) from handle_in | Sends phx_reply; the event arg is ignored on the wire — reply is keyed by ref |
| Push to the current socket only | channel.Push(event, payload, socket) from handle_in or handle_info | Server-originated push on this socket's topic |
| No response | channel.NoReply(socket) | Use when the handler has no output |
| Broadcast to all sockets on a topic | beryl.broadcast(registry, topic, event, payload) | All subscribers including the sender |
| Broadcast, excluding sender | beryl.broadcast_from(channels, socket.id(socket), topic, event, payload) | Second arg is except_socket_id: String; use socket.id/1 to extract it when you have a Socket value. Skips the originating socket; works across PubSub nodes |
| Send an OTP message to a channel actor | beryl.send_info(channels, socket_id, topic_name, message) | Delivers to handle_info; the callback receives a Dynamic value — decode and validate it there; no compile-time type checking |
| Broadcast presence diff | beryl.broadcast_presence_diff(registry, topic, diff) | Encodes Phoenix-shaped joins/leaves; only named topic entries are included |
Phoenix wire protocol reference
Section titled “Phoenix wire protocol reference”beryl speaks the same JSON array wire format as Phoenix channels. All frames are JSON arrays with five elements:
[join_ref, ref, topic, event, payload]| Field | Type | Description |
|---|---|---|
join_ref | string or null | Reference from the original phx_join frame; null for server-initiated pushes |
ref | string or null | Per-message reference echoed in the reply; null for pushes |
topic | string | The channel topic, e.g. "room:lobby" |
event | string | Event name |
payload | object | Arbitrary JSON object |
System events
Section titled “System events”| Event | Direction | Meaning |
|---|---|---|
phx_join | client → server | Request to join a topic |
phx_leave | client → server | Unsubscribe from a topic |
phx_reply | server → client | Reply to a client message |
phx_error | server → client | Join rejected or channel error |
phx_close | server → client | Channel closed by server |
heartbeat | client → server | Keep-alive ping (topic "phoenix") |
Reply shape (phx_reply)
Section titled “Reply shape (phx_reply)”Sent in response to any client message. The event arg passed to channel.Reply is not reflected on the wire — the frame always uses phx_reply and the original ref.
[join_ref, original_ref, "topic:name", "phx_reply", {"status": "ok", "response": <your_payload>}]A join reply uses the join_ref as both join_ref and ref:
["1", "1", "room:lobby", "phx_reply", {"status": "ok", "response": {}}]Heartbeat shape
Section titled “Heartbeat shape”The client sends heartbeats on the "phoenix" topic; beryl replies immediately:
// client →[null, "ref", "phoenix", "heartbeat", {}]
// server →[null, "ref", "phoenix", "phx_reply", {"status": "ok", "response": {}}]Presence diff shape
Section titled “Presence diff shape”Follows the Phoenix presence diff format. Both joins and leaves are objects keyed by presence key (typically the user ID). Each value has a metas array:
{ "joins": { "user:42": { "metas": [{ "phx_ref": "abc123", "online_at": 1234567890 }] } }, "leaves": { "user:99": { "metas": [{ "phx_ref": "xyz789" }] } }}broadcast_presence_diff encodes only named topic entries (entries with an explicit key). Anonymous entries are excluded.
Client compatibility
Section titled “Client compatibility”Because beryl uses the standard Phoenix wire format, any Phoenix-compatible WebSocket client works out of the box:
| Client | Notes |
|---|---|
phoenix.js | Official JS client; full support |
phx | Gleam client; designed for beryl |
| Phoenix Swift / Kotlin clients | Community Phoenix clients; wire-compatible |
| Plain WebSocket | Use the JSON array format directly; no reconnect logic |
The WebSocket upgrade path is caller-provided — there is no default. Pass the path when constructing your transport config with mist_transport.default_config(path). The Phoenix JS client appends /websocket to the socket endpoint, so if you configure the client with "/socket", mount your handler at "/socket/websocket". See the WebSocket Transport guide for details.
Pre-1.0 stability policy
Section titled “Pre-1.0 stability policy”beryl follows Semantic Versioning but is not yet 1.0. Until the 1.0 release:
- Minor version bumps (
0.x → 0.x+1) may include breaking changes to the public API. - Patch version bumps (
0.x.y → 0.x.y+1) fix bugs without intentional breakage. - Public API is defined as the exports of the modules listed in the module map above.
- Lower-level modules (e.g.
beryl/coordinator,beryl/wire) are public and usable, but are less stable than the top-level API and are primarily intended for advanced integrations or custom transports. Their signatures may change without a deprecation cycle.
Check GitHub releases and the Migration & Releases page before upgrading to a new minor version.