</> htmx
🚧 htmx 4.0 is under construction. Read changes →

htmx Server-Sent Events (SSE) Extension

The SSE extension adds support for Server-Sent Events streaming to htmx. It works by intercepting any htmx response with Content-Type: text/event-stream and streaming SSE messages into the DOM in real-time.

SSE is a lightweight alternative to WebSockets that works over existing HTTP connections, making it easy to use through proxy servers and firewalls. SSE is uni-directional: the server pushes data to the client. If you need bi-directional communication, consider WebSockets instead.

Installing

Include the extension script after htmx and approve it:

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

Approve the extension via meta tag:

<meta name="htmx-config" content='{"extensions": "sse"}'>

How It Works

The SSE extension hooks into htmx’s request pipeline. When any htmx request receives a response with Content-Type: text/event-stream, the extension takes over and streams SSE messages into the DOM instead of performing a normal swap.

This means any hx-get, hx-post, etc. that returns an SSE stream will just work, no special attributes needed beyond loading the extension.

hx-sse:connect

For persistent SSE connections (auto-connect on load, reconnect on failure), use hx-sse:connect:

<!-- Auto-connects on load, streams messages into the div -->
<div hx-sse:connect="/stream">
    Waiting for messages...
</div>

hx-sse:connect is convenience sugar for a well-preconfigured hx-get. It defaults to:

Using with Standard Attributes

hx-sse:connect works with all standard htmx attributes:

<!-- Swap into a different target -->
<button hx-sse:connect="/notifications" hx-target="#alerts">
    Start Notifications
</button>
<div id="alerts"></div>

<!-- Append messages instead of replacing -->
<div hx-sse:connect="/log" hx-swap="beforeend">
    <h3>Log:</h3>
</div>

Trigger Modifiers

All standard hx-trigger modifiers are supported:

<!-- Connect after a delay -->
<div hx-sse:connect="/stream" hx-trigger="load delay:2s">

<!-- Connect on click -->
<button hx-sse:connect="/stream" hx-trigger="click">Start</button>

<!-- Connect on click, only once -->
<button hx-sse:connect="/stream" hx-trigger="click once">Start</button>

Using Standard htmx Attributes

Since the extension intercepts based on Content-Type, any htmx request that returns text/event-stream will be streamed automatically:

<!-- hx-get, hx-post, etc. all work -->
<div hx-get="/stream" hx-trigger="load">
    Waiting...
</div>

<button hx-post="/generate" hx-target="#output">
    Generate
</button>

The difference is that hx-sse:connect enables reconnection and pauseOnBackground by default, while standard attributes do not.

hx-sse:close

Use hx-sse:close to gracefully close an SSE connection when a specific named event is received from the server:

<div hx-sse:connect="/stream" hx-sse:close="done">
    Streaming until server sends "done"...
</div>

When the server sends event: done, the connection is closed and an htmx:sse:close event is fired with detail.reason === "message".

Named Events

SSE messages with an event: field are dispatched as DOM events on the source element rather than being swapped:

event: notification
data: {"title": "New message", "body": "Hello!"}
<div hx-sse:connect="/events"
     hx-on:notification="alert(event.detail.data)">
</div>

Messages without an event: field are swapped into the DOM as HTML content.

Configuration

Configure SSE behavior globally via htmx.config.sse or per-element via hx-config:

<!-- Global config -->
<meta name="htmx-config" content='{
    "sse": {
        "reconnect": true,
        "reconnectDelay": 500,
        "reconnectMaxDelay": 60000,
        "reconnectMaxAttempts": 50,
        "reconnectJitter": 0.3,
        "pauseOnBackground": false
    }
}'>

<!-- Per-element override -->
<div hx-sse:connect="/stream" hx-config='{"sse": {"reconnect": false}}'>
OptionDefault (hx-sse:connect)Default (hx-get)Description
reconnecttruefalseAuto-reconnect on stream end
reconnectDelay500500Initial reconnect delay (ms)
reconnectMaxDelay6000060000Maximum reconnect delay (ms)
reconnectMaxAttemptsInfinityInfinityMaximum reconnection attempts
reconnectJitter0.30.3Jitter factor (0-1) for delay randomization
pauseOnBackgroundtruefalseClose the stream when the tab is backgrounded, reconnect when visible

Reconnection Strategy

The extension uses exponential backoff with jitter:

Events

htmx:before:sse:connection

Fired before a connection attempt (initial or reconnection). Set detail.connection.cancelled = true to prevent the connection.

For reconnections (detail.connection.attempt > 0), you can also modify detail.connection.delay to change the backoff delay.

document.body.addEventListener('htmx:before:sse:connection', function(evt) {
    if (evt.detail.connection.attempt > 10) {
        evt.detail.connection.cancelled = true;
    }
});

htmx:after:sse:connection

Fired after a successful connection (or reconnection) to the SSE stream.

htmx:before:sse:message

Fired before each SSE message is processed. All fields are modifiable. Changes to data or event affect how the message is handled.

document.body.addEventListener('htmx:before:sse:message', function(evt) {
    // Skip heartbeats
    if (evt.detail.message.event === 'heartbeat') {
        evt.detail.message.cancelled = true;
    }

    // Transform data before swap
    evt.detail.message.data = sanitize(evt.detail.message.data);
});

htmx:after:sse:message

Fired after an SSE message has been processed.

htmx:sse:error

Fired when a stream error occurs.

htmx:sse:close

Fired when an SSE connection is closed.

Additional Resources