HTMX WebSockets

WebSockets provide full-duplex communication between the browser and the server — both sides can send data at any time over a single persistent connection. Unlike Server-Sent Events (SSE), which only push data from server to client, WebSockets allow the client to send messages to the server too. HTMX supports WebSockets through an official extension, making it possible to build real-time collaborative and chat features with HTML attributes.

SSE vs WebSockets: When to Use Each

FeatureServer-Sent EventsWebSockets
DirectionServer → Client onlyServer ↔ Client (both directions)
ProtocolHTTPWS / WSS (own protocol)
ReconnectionAutomaticManual (must handle in code)
Best forNotifications, feeds, dashboardsChat, collaborative editing, games
ComplexityLowMedium

Setting Up the WebSocket Extension

<script src="https://unpkg.com/htmx.org@2.0.0"></script>
<script src="https://unpkg.com/htmx-ext-ws@2.0.1/ws.js"></script>

Connecting to a WebSocket

<div
  hx-ext="ws"
  ws-connect="/ws/chat">

  <!-- Messages from the server appear here -->
  <div id="chat-messages"></div>

  <!-- Form sends messages to the server -->
  <form ws-send>
    <input type="text" name="message" placeholder="Type a message...">
    <button type="submit">Send</button>
  </form>

</div>
AttributePurpose
hx-ext="ws"Activates the WebSocket extension on this element
ws-connect="/url"The WebSocket endpoint to connect to (ws:// or wss://)
ws-sendPlaced on a form — sends the form data through the WebSocket when submitted

How the Message Flow Works

  Browser opens WebSocket connection to /ws/chat

  User types "Hello!" and clicks Send
         |
         v
  ws-send serializes the form: message=Hello!
         |
         v
  Data sent through WebSocket to server
         |
         v
  Server broadcasts to all connected clients:
    <div hx-swap-oob="beforeend:#chat-messages">
      <p><strong>Alice:</strong> Hello!</p>
    </div>
         |
         v
  HTMX receives the HTML, processes OOB swap
         |
         v
  New message appears in #chat-messages for all users

Server-Side WebSocket Handler

Python (using websockets library)

import asyncio
import websockets
import json

connected_clients = set()

async def chat_handler(websocket, path):
    connected_clients.add(websocket)
    try:
        async for raw_message in websocket:
            data = json.loads(raw_message)
            message_text = data.get('message', '').strip()

            if message_text:
                # Build the HTML fragment
                html = f'''
                <div hx-swap-oob="beforeend:#chat-messages">
                  <p><strong>User:</strong> {message_text}</p>
                </div>
                '''
                # Broadcast to all connected clients
                websockets.broadcast(connected_clients, html)
    finally:
        connected_clients.discard(websocket)

asyncio.run(websockets.serve(chat_handler, "localhost", 8765))

Node.js (using ws library)

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8765 });
const clients = new Set();

wss.on('connection', (ws) => {
    clients.add(ws);

    ws.on('message', (raw) => {
        const data = JSON.parse(raw);
        const msg  = data.message || '';
        const html = `
          <div hx-swap-oob="beforeend:#chat-messages">
            <p><strong>User:</strong> ${msg}</p>
          </div>`;

        clients.forEach(client => {
            if (client.readyState === WebSocket.OPEN) {
                client.send(html);
            }
        });
    });

    ws.on('close', () => clients.delete(ws));
});

Full Chat UI Example

<div hx-ext="ws" ws-connect="wss://example.com/ws/chat">

  <div id="chat-messages" style="height:400px; overflow-y:auto; border:1px solid #ccc; padding:10px">
    <p>Connected to chat. Say hello!</p>
  </div>

  <form ws-send style="display:flex; gap:8px; margin-top:10px">
    <input
      type="text"
      name="message"
      placeholder="Type a message..."
      autocomplete="off"
      style="flex:1">
    <button type="submit">Send</button>
  </form>

</div>
  Visual:

  ┌──────────────────────────────────────────────┐
  │ Connected to chat. Say hello!                │
  │ Alice: Hello everyone!                       │
  │ Bob: Hey Alice!                              │
  │ Alice: How's the project going?              │
  └──────────────────────────────────────────────┘
  [Type a message...                    ] [Send]

Handling WebSocket Events in JavaScript

The HTMX WebSocket extension fires events you can listen to for connection status management:

<script>
document.body.addEventListener('htmx:wsOpen', function() {
    document.getElementById('status').textContent = 'Connected';
});

document.body.addEventListener('htmx:wsClose', function() {
    document.getElementById('status').textContent = 'Disconnected — attempting reconnect...';
});

document.body.addEventListener('htmx:wsError', function(event) {
    console.error('WebSocket error:', event.detail);
});
</script>

<span id="status">Connecting...</span>

Reconnection Strategy

Unlike SSE, WebSockets do not reconnect automatically. The HTMX WebSocket extension handles reconnection, but you should display a status indicator and potentially implement exponential backoff for unstable connections:

<script>
let reconnectDelay = 1000;

document.body.addEventListener('htmx:wsClose', function() {
    setTimeout(() => {
        // htmx.process re-initializes the WS element, triggering reconnect
        htmx.process(document.getElementById('chat-container'));
        reconnectDelay = Math.min(reconnectDelay * 2, 30000); // Max 30s
    }, reconnectDelay);
});
</script>

Security: Always Use WSS in Production

Use ws:// only in local development. In production, always use wss:// (WebSocket Secure) — the WebSocket equivalent of HTTPS. Data sent over ws:// travels in plain text and can be intercepted.

Key Takeaway

HTMX's WebSocket extension enables two-way real-time communication using HTML attributes. Connect with ws-connect and send form data with ws-send. The server receives the message, builds an HTML fragment, and broadcasts it back. HTMX processes the incoming HTML and updates the DOM. Use WebSockets when you need the client to send data to the server in real time — chat, collaborative tools, and live games. Use SSE when you only need the server to push updates to the client.

Leave a Comment

Your email address will not be published. Required fields are marked *