HTMX v2 to v4 Migration Guide¶
This guide covers the breaking changes when upgrading from htmx v2 to v4 in Vibetuner projects.
SSE: Native Support Replaces Extension¶
htmx v4 includes Server-Sent Events support in core. The separate sse extension
and hx-ext="sse" attribute are no longer needed.
Before (v2):
<div hx-ext="sse" sse-connect="/events/notifications" sse-swap="update">
<!-- updates appear here -->
</div>
After (v4):
The sse-connect and sse-swap attributes work exactly the same — just remove
hx-ext="sse" from the element.
Extension Auto-Registration¶
In v2, extensions had to be explicitly activated via hx-ext="..." on each element
or a parent. In v4, extensions auto-register when imported — no hx-ext attribute
needed.
Before (v2):
After (v4):
Extensions activate automatically once their script is loaded.
hx-vars Replaced by hx-vals with js: Prefix¶
The hx-vars attribute (which evaluated JavaScript expressions) has been removed.
Use hx-vals with the js: prefix instead.
Before (v2):
<button hx-post="/api/action"
hx-vars="csrfToken:getCsrfToken(), timestamp:Date.now()">
Submit
</button>
After (v4):
<button hx-post="/api/action"
hx-vals='js:{"csrfToken": getCsrfToken(), "timestamp": Date.now()}'>
Submit
</button>
Note
Plain hx-vals (without js: prefix) still works for static JSON values and
is unchanged.
hx-disable Renamed to hx-ignore¶
The attribute that prevents htmx from processing an element has been renamed.
Before (v2):
After (v4):
Attribute Inheritance Requires :inherited¶
In v2, many htmx attributes were inherited by child elements automatically. In v4,
inheritance must be explicitly opted into using the :inherited modifier.
Before (v2):
<div hx-target="#results">
<!-- All children inherit hx-target="#results" -->
<button hx-get="/search">Search</button>
<button hx-get="/filter">Filter</button>
</div>
After (v4):
<div hx-target:inherited="#results">
<!-- Children inherit hx-target via :inherited modifier -->
<button hx-get="/search">Search</button>
<button hx-get="/filter">Filter</button>
</div>
Without :inherited, each child element must set its own attributes explicitly.
JavaScript Import Changes¶
htmx v4 uses a default export. The import pattern in your config.js (or equivalent
entry point) must be updated.
Before (v2):
After (v4):
The default import is required, and you must explicitly assign htmx to window for
it to be available globally (e.g., in inline scripts or the browser console).
Preload Extension¶
The preload extension moved from a separate package to a built-in module.
Before (v2):
After (v4):
Common Migration Issues¶
SSE elements stop updating after upgrade¶
Symptom: SSE-powered elements no longer receive updates.
Cause: The hx-ext="sse" attribute was removed but the SSE extension script is
still being loaded, conflicting with htmx v4's built-in SSE support.
Fix: Remove both the hx-ext="sse" attribute and any <script> tag loading
htmx-ext-sse. The sse-connect and sse-swap attributes work natively in v4.
window.htmx is undefined in inline scripts¶
Symptom: Inline <script> tags or browser console show htmx is not defined.
Cause: htmx v4 uses a default export that must be explicitly assigned to window.
Fix: Update your JS entry point:
Attributes no longer inherited by child elements¶
Symptom: Buttons or links inside a container stop working because they relied on
a parent's hx-target, hx-swap, or similar attribute.
Cause: htmx v4 no longer inherits attributes by default.
Fix: Add :inherited to the parent attribute:
Preload extension not working¶
Symptom: preload="mouseover" has no effect after upgrade.
Cause: The import path changed and the old hx-ext="preload" is no longer needed.
Fix: Update your import and remove hx-ext:
hx-vars attribute ignored¶
Symptom: Dynamic values previously set via hx-vars are no longer sent.
Cause: hx-vars was removed in v4.
Fix: Use hx-vals with the js: prefix:
Migration Checklist¶
- [ ] Remove all
hx-ext="sse"attributes from SSE elements - [ ] Remove all other
hx-ext="..."attributes (extensions auto-register) - [ ] Replace
hx-varswithhx-valsusingjs:prefix - [ ] Replace
hx-disablewithhx-ignore - [ ] Add
:inheritedmodifier to attributes that rely on inheritance - [ ] Update JS imports to use default import and
window.htmx = htmx - [ ] Update preload extension import path
- [ ] Remove
htmx-ext-sseandhtmx-ext-preloadfrompackage.jsonif present