For htmx 2 + 4

Server HTML,
instantly useful.

A tiny htmx extension for stale-while-revalidate caching, retries, request deduplication, and optimistic updates—without a client-side data store.

your-page.html
<!-- one attribute, instant cache -->
<ul hx-get="/todos"
    hx-trigger="load"
    hx-swr="30">
</ul>

// Cached for 30s. Stale content appears
// immediately while htmx refreshes it.
7.13 kBminified + brotli
134/134integration tests
9browser/version runs · 3 engines
1 MiBmaximum cache memory
Why htmx-query

The parts you reach for after the first htmx page.

Keep server-rendered HTML and htmx’s request model. Add the response-handling conveniences that make interfaces feel immediate and resilient.

Stale while revalidate

Render a cached fragment immediately, then quietly refresh it when it becomes stale.

Safe retries

Retry GETs with exponential backoff. Unsafe verbs require an explicit opt-in.

One request, many targets

Collapse identical in-flight GETs; each target renders when the winner lands.

+

Optimistic HTML

Append a developer-authored pending template and remove only its nodes on failure.

Native htmx behavior

Cached swaps preserve hx-swap and hx-select behavior.

No data layer

No JSON cache, client store, offline queue, or SPA rewrite. Just rendered HTML.

Install

Two scripts. One extension.

  1. Load htmx 2 or 4Supports >=2.0.0 <3 || >=4.0.0-beta6 <5.
  2. Load htmx-queryThe CDN build self-registers when window.htmx exists.
  3. Activate when neededhtmx 4 extensions are global; only htmx 2 needs hx-ext="query".
<script src="https://unpkg.com/htmx.org@4.0.0-beta6"></script>
<script src="https://unpkg.com/htmx-query@0.2.0"></script>

<body>
  ...
</body>

htmx 2: use htmx.org@2.0.10 and add hx-ext="query". Production: pin exact CDN versions and add Subresource Integrity hashes. Floating versions cannot be integrity-checked.

Bundlers

Register explicitly

Use the ESM build when htmx is supplied by your bundler.

import htmx from 'htmx.org';
import { register } from 'htmx-query';

register(htmx);
Examples

Use the attributes you already know.

Cache + SWR

Make a fragment feel instant.

A fresh hit cancels the network request. A stale hit renders now and refreshes in the background.

<ul hx-get="/todos"
    hx-trigger="load, refresh"
    hx-swr="30"></ul>

<!-- later: htmx.trigger(list, 'refresh') -->
Retry

Recover from a flaky GET.

Retries use exponential backoff. POST requests are never retried unless you explicitly opt in.

<button hx-get="/reports/today"
  hx-target="#report"
  hx-retry="3"
  hx-retry-delay="500">
  Refresh report
</button>
Optimistic update

Show the pending row immediately.

The template is removed before a real response swap and on request failure.

<form hx-post="/todos"
  hx-target="#list" hx-swap="beforeend"
  hx-optimistic="#pending">...</form>
<template id="pending"><li>Saving…</li></template>
Reference

A small attribute surface.

AttributeUse
hx-swr="TTL"Cache a GET response; fresh entries cancel the request, stale entries revalidate.
hx-swr-key="key"Override the default verb:final URL cache key.
hx-swr-vary="Header"Opt in a safe request header such as Accept-Language as a key dimension.
hx-swr-prefetch="hover focus visible"One same-origin GET on hover, keyboard focus, and/or viewport entry that fills the cache without swapping the source.
hx-retry="N"Retry an eligible failed request up to N times (maximum 10).
hx-retry-delay="ms"Set the retry backoff base; defaults to 1000 ms.
hx-retry-unsafeAllow retries for non-GET requests.
hx-optimistic="#template"Append pending template nodes until the request resolves.
Imperative helpers

Invalidate when your mutation changes a resource.

htmx.query.invalidate('/todos'); // remove matching cached entries
htmx.query.clear();               // clear every entry
htmx.query.peek();                // Map copy, for debugging
htmx.query.stats();               // cache/dedupe counters and memory
htmx.query.setNamespace('acme'); // clear old account data, then scope future keys
document.body.addEventListener('hq:cache', observe); // hit/miss/store/evict/skip/clear

HTTP and account safety: cached HTML is server-rendered HTML and is reinserted verbatim. Age and Date reduce freshness; Expires provides a TTL when max-age is absent. no-store, private, and unkeyed request-header variation are never retained. An ETag entry revalidates with If-None-Match. Send HX-Cache-Invalidate: {"path":"/todos","mode":"path"} after a mutation to clear matching entries. Call setNamespace(accountId) after account changes.

For AI coding agents

Give your agent the real rules.

llms.txt describes the API, invariants, source layout, test commands, and safe modification rules in a compact form. It is the fastest way for an agent to understand this repository before changing it.

Open llms.txt ↗