HTMX Response Headers

Just as HTMX sends special headers with every request, your server can send special headers back in the response. These response headers let the server control HTMX behavior from the server side — triggering client-side events, redirecting to a new URL, pushing a new URL into the browser history, and more. This two-way header system is one of the most powerful features in HTMX.

The Two-Way Header System

  CLIENT  ──── HTMX Request Headers ────►  SERVER
               (who triggered, target, URL)

  CLIENT  ◄─── HTMX Response Headers ────  SERVER
               (redirect, push URL, trigger event)

Complete Response Header Reference

HeaderWhat It Does
HX-TriggerFires a client-side event after the response is processed
HX-Trigger-After-SettleFires an event after the CSS settle period completes
HX-Trigger-After-SwapFires an event after the DOM swap completes
HX-LocationPerforms a client-side HTMX redirect without a full page reload
HX-RedirectForces a full browser redirect to a new URL
HX-Push-UrlPushes a new URL into the browser's history stack
HX-Replace-UrlReplaces the current URL in the browser's history (no new history entry)
HX-ReswapOverrides the hx-swap value specified on the element
HX-RetargetOverrides the hx-target value specified on the element
HX-RefreshTriggers a full page refresh when set to "true"

HX-Trigger: Fire Client Events From the Server

The HX-Trigger response header tells HTMX to fire a named event on the client after the response is processed. This lets the server coordinate multiple UI updates beyond the main swap.

Flask Example

from flask import Response, render_template

@app.route('/add-to-cart', methods=['POST'])
def add_to_cart():
    # ... add item to cart ...
    response = Response(
        render_template('partials/cart-item.html'),
        status=200
    )
    response.headers['HX-Trigger'] = 'cart-updated'
    return response

On the client, any element listening for cart-updated responds automatically:

<!-- Cart counter refreshes whenever "cart-updated" fires -->
<div id="cart-count"
     hx-get="/cart/count"
     hx-trigger="cart-updated from:body"
     hx-target="this">
  0 items
</div>
  Flow:

  User clicks "Add to Cart"
       |
       v
  POST /add-to-cart  →  Server adds item
       |
       v
  Server returns cart item HTML + HX-Trigger: cart-updated
       |
       v
  HTMX fires "cart-updated" event on the body
       |
       v
  #cart-count's hx-trigger catches it
       |
       v
  GET /cart/count → updates "3 items"

  All in one user action — no extra JavaScript

Triggering Multiple Events

Send a JSON object in the header value to fire multiple events, optionally with data:

response.headers['HX-Trigger'] = json.dumps({
    "cart-updated": {"item_id": 42, "quantity": 2},
    "show-toast": {"message": "Item added to cart!"}
})

Client-side listeners receive the data in the event's detail object.

HX-Push-Url: Update the Browser URL Without a Reload

When HTMX updates a panel, the browser URL does not change by default. Use HX-Push-Url to push the relevant URL into the history, so the back button and sharing work correctly:

@app.route('/products/<int:product_id>')
def product_detail(product_id):
    product = Product.query.get(product_id)
    response = Response(render_template('partials/product-detail.html', product=product))
    response.headers['HX-Push-Url'] = f'/products/{product_id}'
    return response
  Before click:  browser URL = /products
  User clicks on Product #42
  HTMX loads product detail into the panel
  After click:   browser URL = /products/42  ← pushed by HX-Push-Url
  User presses Back:  browser URL = /products ← history entry restored

HX-Redirect: Force a Full Page Navigation

After a successful login, you often need to redirect the user to the dashboard. Because HTMX only swaps fragments, a regular server-side redirect just loads the dashboard into the current target div. Use HX-Redirect to force a proper full-page navigation:

@app.route('/login', methods=['POST'])
def login():
    user = authenticate(request.form)
    if user:
        session['user_id'] = user.id
        response = Response('', status=200)
        response.headers['HX-Redirect'] = '/dashboard'
        return response
    return render_template('partials/login-error.html'), 401

HX-Reswap and HX-Retarget: Override Client Attributes

These headers let the server change where and how the response is inserted — overriding whatever hx-swap and hx-target the element specified. This is useful when the server's response type determines the swap strategy:

# On error: retarget to the error panel and change swap strategy
@app.route('/process', methods=['POST'])
def process():
    try:
        result = do_processing()
        return render_template('partials/success.html', result=result)
    except Exception as e:
        response = Response(
            render_template('partials/error.html', message=str(e)),
            status=422
        )
        response.headers['HX-Retarget'] = '#error-panel'
        response.headers['HX-Reswap']   = 'innerHTML'
        return response

HX-Refresh: Full Page Reload

response.headers['HX-Refresh'] = 'true'

HTMX receives this header and triggers a full browser page reload. Use it as a last resort when the page state has changed so fundamentally that a partial update is not sufficient.

Key Takeaway

HTMX response headers let your server drive client behavior without JavaScript. Use HX-Trigger to fire events that update other parts of the page, HX-Push-Url to maintain a correct browser URL, HX-Redirect to navigate to a new page, and HX-Reswap or HX-Retarget to change how and where the response lands. These headers make the server the single source of truth for application behavior — which is exactly the philosophy HTMX is built on.

Leave a Comment

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