{"record":{"id":"546301e9da272aa1","repo":"bigskysoftware/htmx","slug":"invalid-re-target-target","errorCode":null,"errorMessage":"Invalid re-target ${target}","messagePattern":"Invalid re-target (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/htmx.js","lineNumber":4795,"sourceCode":"        window.document.title = title\n      }\n    }\n  }\n\n  /**\n   * Resove the Retarget selector and throw if not found\n   * @param {Element} elt\n   * @param {String} target\n   * @returns {Element}\n   */\n  function resolveRetarget(elt, target) {\n    if (target === 'this') {\n      return elt\n    }\n    const resolvedTarget = asElement(querySelectorExt(elt, target))\n    if (resolvedTarget == null) {\n      triggerErrorEvent(elt, 'htmx:targetError', { target })\n      throw new Error(`Invalid re-target ${target}`)\n    }\n    return resolvedTarget\n  }\n\n  /**\n   * @param {Element} elt\n   * @param {HtmxResponseInfo} responseInfo\n   */\n  function handleAjaxResponse(elt, responseInfo) {\n    const xhr = responseInfo.xhr\n    let target = responseInfo.target\n    const etc = responseInfo.etc\n    const responseInfoSelect = responseInfo.select\n\n    if (!triggerEvent(elt, 'htmx:beforeOnLoad', responseInfo)) return\n\n    if (hasHeader(xhr, /HX-Trigger:/i)) {\n      handleTriggerHeader(xhr, 'HX-Trigger', elt)","sourceCodeStart":4777,"sourceCodeEnd":4813,"githubUrl":"https://github.com/bigskysoftware/htmx/blob/ad56dff71e55d9c717447437b4c942a64575d4b2/src/htmx.js#L4777-L4813","documentation":"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.","triggerScenarios":"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 src/htmx.js:4854 (responseHandling.target) and src/htmx.js:4863 (HX-Retarget header). The special value 'this' short-circuits and returns elt, so it never throws.","commonSituations":"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. For the editable src/htmx.js copy, fix the source directly.","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 (paste the selector into the Elements panel find box).","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 that is guaranteed present.","If the target is rendered conditionally, ensure it is in the DOM before the request fires (render a hidden placeholder, or issue the request only after the fragment exists), or have the server fall back to omitting HX-Retarget when the element would be absent.","If using extended selector syntax, verify the keyword and the leading space are correct ('closest .container', not 'closest.container') and that elt is a descendant/sibling as the operator requires."],"exampleFix":"// before — server sends a header for an element that may not exist\n//   HX-Retarget: #user-toast\n// and #user-toast is absent on this page -> throws 'Invalid re-target #user-toast'\n\n// after — guard on the client before relying on retarget, and/or make the\n// element always present:\n<div id=\"user-toast\" hidden></div>\n<button hx-post=\"/save\" hx-target=\"#user-toast\">Save</button>\n\n// or capture the event to recover instead of letting the throw kill the request:\ndocument.body.addEventListener('htmx:targetError', (e) => {\n  console.warn('retarget missed, falling back to elt:', e.detail.target)\n})","handlingStrategy":"validation","validationCode":"// Run before issuing an htmx request whose server will send HX-Retarget,\n// to confirm the selector already resolves against the DOM.\nfunction retargetSelectorExists(rootEl, selector) {\n  if (selector === 'this') return true // resolveRetarget short-circuits on 'this'\n  if (!selector || typeof selector !== 'string') return false\n  // htmx extended syntax: leading keyword + space, else plain CSS\n  return document.querySelector(selector) != null\n}\n\n// example gate before triggering the request\nconst btn = document.querySelector('#save-btn')\nconst sel = '#user-toast'\nif (!retargetSelectorExists(btn, sel)) {\n  console.warn('HX-Retarget target missing; skipping request')\n} else {\n  htmx.trigger(btn, 'submit')\n}","typeGuard":"// Narrows a user/template-supplied string to a known-good retarget selector.\n// Uses htmx's own extended-query resolver when htmx is on the page.\nfunction isResolvableRetarget(elt, selector) {\n  if (typeof selector !== 'string' || selector.length === 0) return false\n  if (selector === 'this') return true\n  const resolved = elt instanceof Element\n    ? htmx._('querySelectorExt')(elt, selector)   // internal extended resolver\n    : document.querySelector(selector)\n  return resolved instanceof Element\n}\n\n// usage\n/** @param {unknown} s */\nfunction asRetarget(s) {\n  return typeof s === 'string' && isResolvableRetarget(document.body, s) ? s : null\n}","tryCatchPattern":"// htmx does NOT wrap resolveRetarget in a try/catch you can hook; the supported\n// recovery surface is the htmx:targetError event fired one statement before the\n// throw. Listen on a stable ancestor (e.g. body) and react there.\ndocument.body.addEventListener('htmx:targetError', (e) => {\n  const { target } = e.detail            // the bad selector\n  const triggeringElt = e.target          // the element that issued the request\n  console.error('[htmx] invalid re-target', target, triggeringElt)\n  // optional: surface user-visible feedback, or re-issue against a known fallback\n})\n\n// If you truly need a synchronous try/catch around a manual htmx.ajax call,\n// wrap it — but the throw originates inside the XHR onload handler, so the\n// event listener above is the only reliable recovery point:\ntry {\n  htmx.ajax('POST', '/save', { target: '#safe', values: form })\n} catch (e) {\n  // catches only synchronous setup errors, NOT the async retarget throw\n  console.error(e)\n}","preventionTips":["Treat HX-Retarget and responseHandling[].target as part of your API contract: document which selectors each endpoint emits and add a test asserting the matching element is always rendered on those routes.","Prefer id selectors over class/extended selectors for retargets — ids rarely collide or go missing.","If the target is conditional, omit the HX-Retarget header server-side when the element would be absent, rather than emitting a selector that may not match.","Register one global htmx:targetError listener in your bootstrap file so a missed retarget is always logged (and can post to your error tracker) instead of failing silently after the throw.","Keep responseHandling overrides minimal and code-scoped; a global {code:'4xx', target:'#x'} fires for every 4xx response and is the most common way an unrelated route trips this error."],"tags":["htmx","retarget","hx-retarget","selector","dom","response-handling"],"backgroundTag":null,"analyzedSha":"ad56dff71e55d9c717447437b4c942a64575d4b2","analyzedAt":"2026-08-13T01:40:12.610Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}