Skip to content

Improve WebSocket messages and reconnects - #3910

Open
scriptogre wants to merge 4 commits into
bigskysoftware:four-devfrom
scriptogre:4.0/improve-ws-ext
Open

Improve WebSocket messages and reconnects#3910
scriptogre wants to merge 4 commits into
bigskysoftware:four-devfrom
scriptogre:4.0/improve-ws-ext

Conversation

@scriptogre

@scriptogre scriptogre commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Why

Re-implement htmx 2.0's WebSocket extension "queue messages while disconnected" behavior. Reconnect only when a close is retryable, and give incoming and outgoing messages one hookable lifecycle.

Changes

<meta name="htmx-config"
      content='{"ws":{"reconnectCodes":[1006,1011,1012,1013]}}'>

Code 1000 stops by default. Per-element hx-config overrides global htmx.config.ws values.

Name events by direction

Before After
htmx:before:ws:connection htmx:ws:before:connection
htmx:after:ws:connection htmx:ws:after:connection
htmx:before:ws:request htmx:ws:before:message:outgoing
htmx:after:ws:request htmx:ws:after:message:outgoing
htmx:before:ws:message htmx:ws:before:message:incoming
htmx:after:ws:message htmx:ws:after:message:incoming

Align with htmx:<extension>:<timing>:<noun>.

Expose message-shaped hooks

Outgoing values move from a nested body to top-level keys. headers is reserved for htmx metadata.

// Before
{ "headers": { /* ... */ }, "body": { "message": "Hello" } }

// After
{ "headers": { /* ... */ }, "message": "Hello" }

Incoming hooks preserve native data and provide cached conversions:

event.detail = {
  message: { data, type, text(), json(), blob(), arrayBuffer() },
  waitUntil,
  cancelled
}

Correlate replies with senders

One shared WebSocket can carry messages from many send elements. hx-ws adds a unique HX-Message-ID to each outgoing message.

The server can copy that ID into an incoming message:

// Browser to server
{ "headers": { "HX-Message-ID": "abc123" }, "action": "save" }

// Server to browser
{ "headers": { "HX-Message-ID": "abc123" }, "content": "<p>Saved</p>" }

The incoming message then uses the original sender for inherited attributes, relative targets, swaps, and events. The first matching incoming message consumes the association. ws.pendingMessageTTL controls how long that sender mapping remains available.

Docs

Docs cover the new behavior and Beta-to-RC1 migration, including:

  • HX-Request-ID to HX-Message-ID
  • ws.pendingRequestTTL to ws.pendingMessageTTL

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant