htmx 4.0 is under construction — migration guide

WebSockets

Enable bidirectional real-time communication via WebSockets

The WebSocket extension enables real-time, bidirectional communication with WebSocket servers directly from HTML. It manages connections efficiently through reference counting, automatic reconnection, and seamless integration with htmx’s swap and event model.

Installing

<script src="/path/to/htmx.js"></script> <script src="/path/to/ext/hx-ws.js"></script>

For npm-style build systems:

import 'htmx.org'; import 'htmx.org/dist/ext/hx-ws.js';

Usage

AttributeDescription
hx-ws:connect="<url>"Establishes a WebSocket connection to the specified URL
hx-ws:sendSends form data or hx-vals to the WebSocket on trigger
hx-ws:send="<url>"Like hx-ws:send but creates its own connection to the URL

JSX-Compatible Variants: For frameworks that don’t support colons in attribute names, use hyphen variants: hx-ws-connect and hx-ws-send.

Basic Example

<div hx-ws:connect="/chatroom" hx-target="#messages" hx-swap="beforeend"> <div id="messages"></div> <form hx-ws:send> <input name="message" placeholder="Type a message..."> <button type="submit">Send</button> </form> </div>

This example:

  1. Establishes a WebSocket connection to /chatroom when the page loads
  2. Appends incoming HTML messages to #messages
  3. Sends form data as JSON when the form is submitted

Receiving Messages

JSON Envelope Format

Messages from the server should be JSON objects:

{ "channel": "ui", "format": "html", "target": "#notifications", "swap": "beforeend", "payload": "<div class='notification'>New message!</div>", "request_id": "abc-123" }
FieldDefaultDescription
channel"ui"Message routing channel
format"html"Content format
targetElement’s hx-targetCSS selector for target element
swapElement’s hx-swapSwap strategy (innerHTML, beforeend, etc.)
payloadThe content to swap
request_idMatches response to original request

Minimal Example (using all defaults):

{"payload": "<div>Hello World</div>"}

Channels

  • ui channel (default): HTML content is swapped into the target element using htmx’s swap pipeline
  • Custom channels: Emit an htmx:wsMessage event for application handling
document.addEventListener('htmx:wsMessage', (e) => { if (e.detail.channel === 'notifications') { showNotification(e.detail.payload); } });

Sending Messages

When an element with hx-ws:send is triggered, the extension sends a JSON message:

{ "type": "request", "request_id": "550e8400-e29b-41d4-a716-446655440000", "event": "submit", "headers": { "HX-Request": "true", "HX-Current-URL": "https://example.com/chat" }, "values": { "message": "Hello!" }, "path": "wss://example.com/chatroom", "id": "chat-form" }

Modifying Messages Before Send

document.addEventListener('htmx:before:ws:send', (e) => { e.detail.data.headers['Authorization'] = 'Bearer ' + getToken(); if (!isValid(e.detail.data)) { e.preventDefault(); } });

Configuration

Configure the extension via htmx.config.websockets:

htmx.config.websockets = { reconnect: true, // Enable auto-reconnect (default: true) reconnectDelay: 1000, // Initial reconnect delay in ms (default: 1000) reconnectMaxDelay: 30000, // Maximum reconnect delay in ms (default: 30000) reconnectJitter: true, // Add randomization to delays (default: true) pendingRequestTTL: 30000 // Time-to-live for pending requests in ms (default: 30000) };

Reconnection Strategy

The extension uses exponential backoff with optional jitter:

  • Base formula: delay = min(reconnectDelay * 2^(attempts-1), reconnectMaxDelay)
  • Jitter: Adds +/-25% randomization to avoid thundering herd
  • Reset: Attempts counter resets to 0 on successful connection

Connection Management

Reference Counting

Multiple elements can share a single WebSocket connection:

<div hx-ws:connect="/notifications" id="notif-1"></div> <div hx-ws:connect="/notifications" id="notif-2"></div>

When all elements using a connection are removed from the DOM, the connection is automatically closed.

Events

Connection Lifecycle

EventCancelableDetailDescription
htmx:before:ws:connectYes{url}Before establishing connection
htmx:after:ws:connectNo{url, socket}After successful connection
htmx:ws:closeNo{url, code, reason}When connection closes
htmx:ws:errorNo{url, error}On connection error
htmx:ws:reconnectNo{url, attempts}Before reconnection attempt

Message Events

EventCancelableDetailDescription
htmx:before:ws:sendYes{data, element, url}Before sending (data is modifiable)
htmx:after:ws:sendNo{data, url}After message sent
htmx:before:ws:messageYes{envelope, element}Before processing received message
htmx:after:ws:messageNo{envelope, element}After processing received message
htmx:wsMessageNo{channel, format, payload, ...}For non-UI channel messages

Examples

Live Chat

<div hx-ws:connect="/chat"> <div id="messages" hx-target="this" hx-swap="beforeend"></div> <form hx-ws:send> <input name="message" placeholder="Message..." autocomplete="off"> <button type="submit">Send</button> </form> </div>

Real-Time Dashboard

<div hx-ws:connect="/dashboard"> <div id="cpu-usage">--</div> <div id="memory-usage">--</div> </div>

Server sends targeted updates:

{"target": "#cpu-usage", "payload": "<span>45%</span>"} {"target": "#memory-usage", "payload": "<span>2.3 GB</span>"}

Upgrading from htmx 2.x

Attribute Changes

Old (htmx 2.x)New (htmx 4.x)Notes
ws-connect="<url>"hx-ws:connect="<url>"Or hx-ws-connect for JSX
ws-sendhx-ws:sendOr hx-ws-send for JSX

Event Changes

Old EventNew EventNotes
htmx:wsOpenhtmx:after:ws:connectDifferent detail structure
htmx:wsClosehtmx:ws:closeNow includes code and reason
htmx:wsErrorhtmx:ws:errorSimilar
htmx:wsBeforeMessagehtmx:before:ws:messageDifferent detail structure
htmx:wsAfterMessagehtmx:after:ws:messageDifferent detail structure
htmx:wsConfigSendhtmx:before:ws:sendModify e.detail.data instead
htmx:wsAfterSendhtmx:after:ws:sendSimilar

Message Format Changes

Send payload now includes type, request_id, event, and structured headers object instead of HEADERS string.

Receive format now expects JSON envelope with channel, format, target, swap, payload fields instead of raw HTML or hx-swap-oob.