bigskysoftware/htmx · error · Error
Invalid re-target ${target}
Error message
Invalid re-target ${target} What it means
htmx retargets the swap destination via either the HX-Retarget response header or a target field on a responseHandling config entry matched by HTTP status code. resolveRetarget resolves that selector with querySelectorExt (which supports htmx's extended syntax: 'closest ', 'next ', 'previous ', 'find ', plus standard CSS), and if no element matches it fires the htmx:targetError event and throws, because htmx cannot decide where to place the response body. The throw is intentional and fatal for that request — the swap is aborted.
Source
Thrown at www/themes/htmx-theme/static/js/htmx.js:4795
window.document.title = title
}
}
}
/**
* Resove the Retarget selector and throw if not found
* @param {Element} elt
* @param {String} target
* @returns {Element}
*/
function resolveRetarget(elt, target) {
if (target === 'this') {
return elt
}
const resolvedTarget = asElement(querySelectorExt(elt, target))
if (resolvedTarget == null) {
triggerErrorEvent(elt, 'htmx:targetError', { target })
throw new Error(`Invalid re-target ${target}`)
}
return resolvedTarget
}
/**
* @param {Element} elt
* @param {HtmxResponseInfo} responseInfo
*/
function handleAjaxResponse(elt, responseInfo) {
const xhr = responseInfo.xhr
let target = responseInfo.target
const etc = responseInfo.etc
const responseInfoSelect = responseInfo.select
if (!triggerEvent(elt, 'htmx:beforeOnLoad', responseInfo)) return
if (hasHeader(xhr, /HX-Trigger:/i)) {
handleTriggerHeader(xhr, 'HX-Trigger', elt)View on GitHub (pinned to ad56dff71e)
Solutions
- Open DevTools -> Network, inspect the failing XHR's Response Headers, read the exact HX-Retarget value, then confirm an element matching it exists in the DOM at response time.
- Add a global listener to capture the offending selector at runtime: document.body.addEventListener('htmx:targetError', e => console.error('bad retarget:', e.detail.target, e.target)).
- Fix the mismatch: correct the typo in the backend header or in htmx.config.responseHandling, or change the selector to one guaranteed present.
- If the target is rendered conditionally, ensure it is in the DOM before the request fires (render a hidden placeholder), or have the server omit HX-Retarget when the element would be absent.
- If you must change htmx itself, edit src/htmx.js (source of truth) and rebuild/re-vendor into www/themes/htmx-theme/static/js/ so all three copies stay in sync — do not patch this theme file alone.
Example fix
// before — server sends HX-Retarget: #user-toast but #user-toast is absent -> throws
// after — make the element always present, then retarget safely:
<div id="user-toast" hidden></div>
<button hx-post="/save" hx-target="#user-toast">Save</button>
// or recover from the throw via the event instead:
document.body.addEventListener('htmx:targetError', (e) => {
console.warn('retarget missed, falling back:', e.detail.target)
}) Defensive patterns
Strategy: validation
Validate before calling
// Run before issuing an htmx request whose server will send HX-Retarget,
// to confirm the selector already resolves against the DOM.
function retargetSelectorExists(rootEl, selector) {
if (selector === 'this') return true // resolveRetarget short-circuits on 'this'
if (!selector || typeof selector !== 'string') return false
return document.querySelector(selector) != null
}
const btn = document.querySelector('#save-btn')
const sel = '#user-toast'
if (!retargetSelectorExists(btn, sel)) {
console.warn('HX-Retarget target missing; skipping request')
} else {
htmx.trigger(btn, 'submit')
} Type guard
function isResolvableRetarget(elt, selector) {
if (typeof selector !== 'string' || selector.length === 0) return false
if (selector === 'this') return true
const resolved = elt instanceof Element
? htmx._('querySelectorExt')(elt, selector)
: document.querySelector(selector)
return resolved instanceof Element
}
/** @param {unknown} s */
function asRetarget(s) {
return typeof s === 'string' && isResolvableRetarget(document.body, s) ? s : null
} Try / catch
// Recovery surface is the htmx:targetError event (fired just before the throw);
// the throw lives in the async XHR onload, so a sync try/catch will not catch it.
document.body.addEventListener('htmx:targetError', (e) => {
const { target } = e.detail
console.error('[htmx] invalid re-target', target, e.target)
}) Prevention
- Treat HX-Retarget and responseHandling[].target as API contract; test that each endpoint's selector is always rendered.
- Prefer id selectors over class/extended selectors for retargets.
- Omit HX-Retarget server-side when the target element would be absent rather than emitting a possibly-unmatched selector.
- Register one global htmx:targetError listener at bootstrap so misses are logged instead of failing silently.
- This file is a theme-bundled vendored copy of src/htmx.js — regenerate it via the theme build so source-level fixes reach the served asset; do not hand-edit it in isolation or it will drift from the other two copies.
When it happens
Trigger: Server returns an HX-Retarget response header whose CSS/extended selector matches zero elements; OR htmx.config.responseHandling contains an entry (e.g. {code:'422', target:'#form-errors'}) whose target selector is absent in the DOM when the XHR resolves. Reached via handleAjaxResponse at www/themes/htmx-theme/static/js/htmx.js:4854 (responseHandling.target) and www/themes/htmx-theme/static/js/htmx.js:4863 (HX-Retarget header). The special value 'this' short-circuits and returns elt, so it never throws.
Common situations: Typo or stale id/class in the HX-Retarget header sent by the backend; target element lives in a fragment that is conditionally rendered and is absent on the current code path; the element existed when the request was issued but was removed by a concurrent swap/removal before the response arrived; an extended selector like 'closest div' or 'next .item' is evaluated relative to elt which has no matching ancestor/sibling; selector targets an element inside a shadow root or different document. NOTE: www/themes/htmx-theme/static/js/htmx.js is a theme-bundled vendored copy, byte-identical to src/htmx.js — fixes in src/ will not reach the served theme asset until the theme is rebuilt/re-vendored; patching only this file creates drift across the three copies.
Related errors
AI-assisted analysis of bigskysoftware/htmx@ad56dff71e (2026-08-13).
Data as JSON: /api/errors/020321ff174ddd47.
Report an issue: GitHub.