# htmx Admin & Architecture Guide

This guide is for architects, platform engineers, and contributors who need to understand how htmx works internally, how to configure and secure it in production, and how the project is built and tested. It covers the library's architecture, the complete configuration surface, security model, build pipeline, and contribution workflow — written from the source at version `2.0.10`.

> The companion **User Guide** documents the `hx-*` attribute vocabulary for application developers. This guide focuses on what runs *under* those attributes and how to operate htmx safely at scale.

---

## 1. Executive Summary

htmx is a client-side JavaScript library that generalizes the hypermedia model of HTML: it lets any element issue AJAX requests and swap the resulting HTML into the DOM, declaratively, via HTML attributes. There is no client-side application state to manage, no JSON serialization layer, and no build step.

| Property | Value |
|----------|-------|
| Version (this analysis) | `2.0.10` |
| Source commit | `5e867de` (2026-06-01) |
| License | `0BSD` (BSD Zero Clause — public-domain-equivalent) |
| Package name | `htmx.org` |
| Language | JavaScript (single file), with TypeScript definitions generated from JSDoc |
| Runtime dependencies | **None** |
| Distribution | CDN (`unpkg`, `jsDelivr`), npm, or self-hosted |
| Homepage | `https://htmx.org/` |

**Architectural thesis:** htmx is a single ~5,600-line IIFE (immediately-invoked function expression) that registers a global event-delegation system. On load, it scans the DOM for `hx-*` attributes, wires up triggers, and intercepts the resulting events to perform XHR requests and DOM swaps. Everything else — history, indicators, out-of-band swaps, view transitions — is built on that core loop.

> [!NOTE]
> For operators, the most important fact about htmx is that it shifts complexity to the **server**. There is no client build artifact to manage beyond a single static `.js` file, but your server now returns HTML fragments and must understand the `HX-*` header protocol. Treat the server as the source of truth and the place where security boundaries are enforced.

---

## 2. Repository Structure

The repository contains the library itself, its documentation website, editor tooling, and an extensive test suite.

```text
htmx/
├── src/
│   ├── htmx.js              # The entire library (one IIFE, ~5,600 lines)
│   └── htmx.esm.d.ts        # Hand-maintained / generated TypeScript definitions
├── dist/                    # Build output (not in source tree; produced by scripts/dist.sh)
│   ├── htmx.js              # UMD build
│   ├── htmx.min.js          # Minified (CDN entry: jsdelivr/unpkg)
│   ├── htmx.esm.js          # ES module (package "main")
│   └── htmx.esm.d.ts        # Generated type declarations (package "types")
├── editors/
│   └── jetbrains/
│       └── htmx.web-types.json   # Web-Types metadata for JetBrains IDE autocomplete
├── scripts/                 # Build & release shell/Node scripts (dist.sh, www.sh, sha.sh, ...)
├── test/
│   ├── attributes/          # Per-attribute behavior tests (32 files)
│   ├── core/                # Core engine tests (17 files: api, ajax, events, ...)
│   ├── util/                # Test utilities
│   ├── lib/                 # Vendored test libs (e.g. hyperscript)
│   └── manual/              # Browser-driven manual test pages (history, hx-boost, ...)
└── www/                     # The htmx.org website (Hugo static site)
    ├── content/             # Markdown docs: attributes/, examples/, essays/, posts/, extensions/
    ├── templates/           # Hugo templates and shortcodes
    ├── themes/htmx-theme/   # The site theme
    └── static/              # Static assets (and a mirrored copy of the test suite)
```

> [!IMPORTANT]
> The `www/static/test/` tree is a **mirror** of the top-level `test/` directory, served by the website so tests can be run in the browser at htmx.org. When contributing, edit the canonical `test/` files — the static copy is generated/synced, not authored directly.

| Directory | Role | Audience |
|-----------|------|----------|
| `src/` | The library source — the only file that ships | Contributors |
| `dist/` | Build artifacts consumed by users | Release |
| `test/` | Automated test suite (Mocha + Chai, run via web-test-runner) | Contributors |
| `www/` | Documentation site (htmx.org) | Docs contributors |
| `editors/` | IDE integration metadata | Tooling |
| `scripts/` | Build, type-generation, and release automation | Maintainers |

---

## 3. Architecture Deep Dive

### 3.1 The IIFE and the public object

The entire library is wrapped in `var htmx = (function() { 'use strict'; ... })()`. Inside, a single public object is constructed and returned. Its shape (from the source) is:

```text
htmx = {
  // Event processing
  onLoad, process, on, off, trigger, ajax,
  // DOM querying helpers
  find, findAll, closest, values,
  // DOM manipulation helpers
  remove, addClass, removeClass, toggleClass, takeClass, swap,
  // Extension entry points
  defineExtension, removeExtension,
  // Debugging
  logAll, logNone, logger,
  // Configuration
  config: { ... },
  // Misc
  parseInterval, location, _ (internalEval), version
}
```

A quirk worth knowing for contributors: the public methods are declared as `null` in the object literal and **reassigned** immediately afterward (`htmx.process = processNode`, etc.). The inline comment explains this is a TypeScript-compiler workaround — assigning functions directly produced invalid `.d.ts` output, but reassignment is fine.

### 3.2 The internal API

A second object, `internalAPI`, is exposed to **extensions** (via the `init(api)` callback). It surfaces lower-level functions that extensions need but that are not part of the stable public surface:

```text
internalAPI = {
  addTriggerHandler, bodyContains, canAccessLocalStorage, findThisElement,
  filterValues, swap, hasAttribute, getAttributeValue, getClosestAttributeValue,
  getClosestMatch, getExpressionVars, getHeaders, getInputValues, getInternalData,
  getSwapSpecification, getTriggerSpecs, getTarget, makeFragment, mergeObjects,
  makeSettleInfo, oobSwap, querySelectorExt, settleImmediately, shouldCancel,
  triggerEvent, triggerErrorEvent, withExtensions
}
```

> [!WARNING]
> The `internalAPI` is intentionally not documented as a public contract. Extensions that reach into it are coupling to htmx internals and may break across minor versions. Prefer the documented extension hooks (`onEvent`, `transformResponse`, `handleSwap`, `isInlineSwap`, `encodeParameters`, `getSelectors`).

### 3.3 Node processing and per-element state

When htmx processes a node (`processNode`), it:

1. Reads `hx-trigger` (or infers a default trigger from the element type).
2. Parses trigger specifications into structured objects (`HtmxTriggerSpecification`).
3. Registers event listeners that, when fired, issue the AJAX request.
4. Computes an **attribute hash** (`attributeHash`) so it can detect when an element's attributes change and avoid re-initializing unchanged nodes.

Per-element private state (in-flight XHR, timeouts, listener registrations, polling state, the request queue, etc.) is stored on the element under a non-enumerable `htmx-internal-data` property, retrieved via `getInternalData(elt)`. This keeps htmx's bookkeeping off the public DOM surface.

### 3.4 Attribute inheritance

Almost every `hx-*` attribute is **inherited** by descendants. Resolution walks up the ancestor chain (`getClosestMatch` / `getClosestAttributeValue`) until it finds the attribute. Two attributes modulate this:

- `hx-disinherit="<attrs>"` — a parent declares which attributes its descendants should **not** inherit.
- `hx-inherit="<attrs>"` — used when `htmx.config.disableInheritance` is `true` to opt specific attributes back into inheritance.

Setting `htmx.config.disableInheritance = true` flips the model from opt-out to opt-in across the whole page.

### 3.5 The request → swap pipeline

```text
issueAjaxRequest(verb, path, elt, event)
  ├─ resolve target (hx-target) and swap spec (hx-swap)
  ├─ gather input values (form data, hx-include, hx-params filter, hx-vals)
  ├─ assemble headers (getHeaders → HX-Request, HX-Trigger, HX-Target, ...)
  ├─ htmx:confirm / htmx:configRequest / htmx:beforeRequest  (cancelable)
  ├─ open XMLHttpRequest, add htmx-request class
  ├─ on response → handleAjaxResponse:
  │     ├─ honor response headers (HX-Location, HX-Redirect, HX-Refresh,
  │     │   HX-Retarget, HX-Reswap, HX-Reselect, HX-Trigger*)
  │     ├─ resolve response handling (status code → swap? error?)
  │     ├─ htmx:beforeSwap  (can re-target / rewrite content / cancel)
  │     ├─ saveCurrentPageToHistory (if a history update is pending)
  │     ├─ swap(target, content, swapSpec)  → swapWithStyle dispatch
  │     │     ├─ OOB swaps (hx-swap-oob)
  │     │     ├─ title update, scroll/show, focus & selection preservation
  │     │     └─ settle phase (htmx-settling, attribute settling)
  │     └─ push/replace URL into history (if configured)
  └─ htmx:afterRequest, remove htmx-request class
```

### 3.6 Swap-style dispatch

`swapWithStyle()` is a `switch` over the swap style. The native styles are `none`, `outerHTML`, `afterbegin`, `beforebegin`, `beforeend`, `afterend`, `delete`, and `innerHTML` (the default fallthrough). `textContent` is handled earlier in `swap()` (it inserts text without HTML parsing). Any **unrecognized** swap style is offered to registered extensions' `handleSwap` hooks; if no extension claims it, htmx falls back to `htmx.config.defaultSwapStyle`. This is the extension point used by morphing extensions (e.g. Idiomorph).

### 3.7 Response handling configuration

The status-code → behavior mapping is data-driven via `htmx.config.responseHandling`, which defaults to:

```javascript
responseHandling: [
  { code: '204', swap: false },                  // No Content: never swap
  { code: '[23]..', swap: true },                // 2xx/3xx: swap
  { code: '[45]..', swap: false, error: true }   // 4xx/5xx: error, no swap
]
```

Each entry can additionally specify `target`, `select`, `swapOverride`, `ignoreTitle`, and a client `event` to fire. This lets operators define application-wide policies (e.g. "swap 422 validation responses into the form") without per-element attributes.

> [!TIP]
> Customizing `responseHandling` is the cleanest way to make error responses (like `422 Unprocessable Entity` from server-side form validation) swap their body into the page, instead of being silently dropped as errors.

---

## 4. Configuration Reference

All runtime configuration lives on `htmx.config`. Set it in a script after htmx loads, or declaratively with a meta tag:

```html
<meta name="htmx-config" content='{"defaultSwapStyle":"outerHTML","timeout":5000}'>
```

The following table is the **complete** config surface with the exact defaults from the source.

| Key | Type | Default | Purpose |
|-----|------|---------|---------|
| `historyEnabled` | boolean | `true` | Enable browser-history support |
| `historyCacheSize` | number | `10` | Pages kept in `sessionStorage` for history restore |
| `refreshOnHistoryMiss` | boolean | `false` | On a history cache miss, do a full reload instead of an AJAX fetch |
| `defaultSwapStyle` | string | `'innerHTML'` | Swap style when `hx-swap` is omitted |
| `defaultSwapDelay` | number | `0` | ms between response and swap |
| `defaultSettleDelay` | number | `20` | ms between swap and settle |
| `includeIndicatorStyles` | boolean | `true` | Inject CSS so `.htmx-indicator` elements hide until a request runs |
| `indicatorClass` | string | `'htmx-indicator'` | Class revealed during requests |
| `requestClass` | string | `'htmx-request'` | Class added to the triggering element in-flight |
| `addedClass` | string | `'htmx-added'` | Class briefly applied to newly-added nodes |
| `settlingClass` | string | `'htmx-settling'` | Class on targets during settle |
| `swappingClass` | string | `'htmx-swapping'` | Class on targets during swap |
| `allowEval` | boolean | `true` | Allow eval-based features (`hx-vars`, trigger filters, `js:` vals) |
| `allowScriptTags` | boolean | `true` | Execute `<script>` tags in swapped content |
| `inlineScriptNonce` | string | `''` | CSP nonce added to inline scripts htmx re-inserts |
| `inlineStyleNonce` | string | `''` | CSP nonce added to inline styles |
| `attributesToSettle` | string[] | `['class','style','width','height']` | Attributes transitioned during settle |
| `withCredentials` | boolean | `false` | Send cookies/auth headers on cross-site requests |
| `timeout` | number | `0` | Request timeout in ms (`0` = no timeout) |
| `wsReconnectDelay` | string \| fn | `'full-jitter'` | WebSocket reconnect backoff strategy |
| `wsBinaryType` | string | `'blob'` | Binary type for WebSocket data |
| `disableSelector` | string | `'[hx-disable], [data-hx-disable]'` | Selector that disables htmx on a subtree |
| `scrollBehavior` | string | `'instant'` | Behavior for `show`/scroll (`auto`/`instant`/`smooth`) |
| `defaultFocusScroll` | boolean | `false` | Scroll focused elements into view after a swap |
| `getCacheBusterParam` | boolean | `false` | Add a cache-buster param to GET requests |
| `globalViewTransitions` | boolean | `false` | Use the View Transitions API for all swaps |
| `methodsThatUseUrlParams` | string[] | `['get','delete']` | Verbs that encode params in the URL |
| `selfRequestsOnly` | boolean | `true` | **Block requests to non-origin hosts** |
| `ignoreTitle` | boolean | `false` | Never update `document.title` from responses |
| `scrollIntoViewOnBoost` | boolean | `true` | Scroll a boosted target into view |
| `triggerSpecsCache` | object \| null | `null` | Optional cache for parsed trigger specs |
| `disableInheritance` | boolean | `false` | Flip attribute inheritance to opt-in |
| `responseHandling` | array | *(see §3.7)* | Status-code → swap/error policy |
| `allowNestedOobSwaps` | boolean | `true` | Process OOB swaps nested inside the main response |
| `historyRestoreAsHxRequest` | boolean | `true` | Send `HX-Request` on history-miss full reloads |
| `reportValidityOfForms` | boolean | `false` | Report HTML5 validation errors and focus the first invalid input |

> [!NOTE]
> Several defaults changed between htmx 1.x and 2.x. The most operationally significant is **`selfRequestsOnly` now defaults to `true`** (it was `false` in 1.x), hardening the default posture against cross-origin request abuse.

---

## 5. Security Analysis

htmx executes server-returned HTML in the browser, so the trust boundary is the **server response**. The library's security posture is governed primarily by a handful of config flags.

### 5.1 Threat model

| Concern | Mechanism | Default posture |
|---------|-----------|-----------------|
| Cross-origin request abuse | `selfRequestsOnly` | **Blocked by default** (`true`) |
| `eval` / code injection | `allowEval` | Enabled (`true`) — needed for `hx-vars`, trigger filters, `js:` vals |
| Script execution in responses | `allowScriptTags` | Enabled (`true`) — `<script>` in swapped HTML runs |
| CSP compatibility | `inlineScriptNonce`, `inlineStyleNonce` | Empty — set to your per-request nonce |
| Credential leakage | `withCredentials` | Disabled (`true` sends cookies cross-site) |
| Sensitive data in history cache | `hx-history="false"` | Per-page opt-out |

### 5.2 Content Security Policy (CSP)

htmx is CSP-friendly but requires configuration:

- **`script-src` without `unsafe-eval`:** set `htmx.config.allowEval = false`. This disables `hx-vars`, square-bracket trigger event filters, and `js:`/`javascript:` prefixes in `hx-vals`/`hx-headers`. When eval is disallowed, htmx fires an `htmx:evalDisallowedError` event rather than evaluating.
- **Nonce-based inline scripts/styles:** set `inlineScriptNonce` / `inlineStyleNonce` to the nonce your server emits per response. htmx copies the nonce onto any inline `<script>`/`<style>` it re-inserts during a swap (it must re-create script nodes because some browsers won't execute scripts inserted from `<template>` content).

> [!CAUTION]
> `allowScriptTags` defaults to `true`, meaning **any `<script>` tag in a swapped response will execute**. If your server can ever reflect untrusted user content into an htmx response, you have a stored-XSS vector. Either rigorously sanitize/escape on the server, or set `htmx.config.allowScriptTags = false` to strip scripts from swapped content.

### 5.3 Cross-origin requests

With `selfRequestsOnly = true` (the default), htmx validates the request URL against the page origin and refuses non-same-origin requests, firing `htmx:invalidPath`/`htmx:badResponseUrl` paths as appropriate. Only relax this if you have a deliberate cross-origin integration, and pair it with a strict server-side CORS policy.

### 5.4 The `HX-*` header protocol as a security tool

Because htmx sends `HX-Request: true` on every request, servers can:

- Distinguish AJAX fragment requests from full-page navigations and avoid leaking partial templates to direct URL access.
- Use `HX-Redirect`/`HX-Location` to enforce auth redirects from the server without trusting client logic.

> [!TIP]
> Validate the `HX-*` request headers server-side but never *trust* them for authorization — they are set by the client and can be forged. Use them for response shaping (fragment vs. full page), not for access control. Authorization must use your normal session/token mechanisms.

### 5.5 Known community-reported issues

From the project's issue tracker at the analyzed commit, security-relevant or behavior-edge issues to be aware of:

| Issue | Summary |
|-------|---------|
| #3743 | Nested OOB swap performed even when nested OOB is intended to be off (interacts with `allowNestedOobSwaps`) |
| #3189 | With `HX-Redirect`, the `hx-indicator` element is not deactivated |
| #2131 | WebSocket: handling events with `hx-on` reported broken in an older version |

These are functional edge cases rather than vulnerabilities, but operators relying on OOB-swap nesting behavior or redirect-with-indicator flows should test against them.

---

## 6. Build & Development Workflow

htmx ships a single source file but has a small Node-based toolchain for distribution, type generation, and the website. All commands are npm scripts defined in `package.json`.

### 6.1 Prerequisites

| Tool | Why |
|------|-----|
| Node.js + npm | Run scripts, ESLint, TypeScript, uglify |
| Playwright browsers | Headless Chromium/Firefox/WebKit for the test suite |
| Bash | Several scripts (`dist.sh`, `www.sh`, `sha.sh`) are shell scripts |

```bash
git clone https://github.com/bigskysoftware/htmx.git
cd htmx
npm install
```

```text
added N packages in Xs
```

### 6.2 Key npm scripts

| Script | Command (effect) |
|--------|------------------|
| `npm run dist` | `scripts/dist.sh` → build `dist/`, then generate types and web-types |
| `npm run lint` | ESLint over `src/htmx.js`, `test/`, and `scripts/*.mjs` (config `standard` + `mocha`) |
| `npm run format` | ESLint `--fix` over the same files |
| `npm run types-check` | `tsc` type-check of `src/htmx.js` (`--checkJs`, no emit) |
| `npm run types-generate` | Generate `dist/htmx.esm.d.ts` from the built ESM file |
| `npm run web-types-generate` | `node scripts/generate-web-types.mjs` → JetBrains web-types JSON |
| `npm test` | `lint` + `types-check` + `test:chrome` |
| `npm run test:chrome` / `test:firefox` / `test:webkit` | Run the suite in a single browser via Playwright |
| `npm run test:all` | Run across Chromium, Firefox, and WebKit |
| `npm run test:ci` | `lint` + `types-check` + `test:all` (the full gate) |
| `npm run www` | `bash scripts/www.sh` → build/serve the documentation site |
| `npm run sha` / `update-sha` | Compute / update integrity SHAs for releases |

> [!IMPORTANT]
> htmx is authored in **plain JavaScript with JSDoc annotations**, and TypeScript runs in **check-only** mode (`--checkJs`) against the source. The shipped `.d.ts` is *generated* from the built ESM file, not hand-written from scratch. When you change a public function signature, update its JSDoc — that is the source of truth for the generated types.

### 6.3 ESLint configuration

The project extends `eslint-config-standard` plus `plugin:mocha/recommended`, with a deliberately relaxed rule set (many `no-*` rules disabled) reflecting htmx's terse, performance-oriented style. Two rules are notable:

- `space-before-function-paren: ['error', 'never']` — function declarations have no space before the paren.
- `no-restricted-properties` bans `String.prototype.substr` in favor of `.slice`/`.substring`.

---

## 7. Testing Strategy

htmx has an unusually thorough browser-based test suite, which is a key reason it can ship a single un-modularized file with confidence.

| Aspect | Detail |
|--------|--------|
| Frameworks | Mocha (runner) + Chai / chai-dom (assertions) + Sinon (spies/stubs) |
| Execution | `@web/test-runner` driving real browsers via Playwright |
| Browsers | Chromium, Firefox, WebKit |
| WebSocket tests | `mock-socket` for unit tests; a real `ws` server (`test/ws-sse/server.js`) for integration (`npm run ws-tests`) |
| Layout | `test/attributes/` (one file per `hx-*` attribute), `test/core/` (engine: ajax, api, events, history, regressions), `test/manual/` (human-driven browser pages for history & boost edge cases) |

> [!TIP]
> The `test/manual/` pages exist because some behaviors — browser history restoration, `hx-boost` navigation, Safari/iOS history quirks — are difficult to assert in a headless runner. When debugging history or boost issues, open these pages in a real browser; they reproduce the exact scenarios the maintainers use.

To run the full suite the way CI does:

```bash
npm run test:ci
```

```text
> lint && types-check && test:all
... ESLint passes
... tsc type-check passes
... web-test-runner: N tests passed across chromium, firefox, webkit
```

---

## 8. Deployment & Operations

htmx itself is a static asset; "deployment" means how you serve and version it.

### 8.1 Serving strategies

| Strategy | Trade-offs |
|----------|-----------|
| Public CDN (`unpkg`/`jsDelivr`) | Zero hosting; relies on third-party availability and trust. Pin exact versions. |
| Self-hosted minified file | Full control, same-origin, CSP-simple. You own cache headers and updates. |
| Bundled (npm import) | Integrates with your build; ensure `window.htmx` is set if you use inline attributes. |

### 8.2 Operational checklist

- [ ] Pin an exact htmx version (avoid floating `@latest` / major-only tags).
- [ ] Add Subresource Integrity (`integrity` + `crossorigin`) if loading from a CDN.
- [ ] Decide and set the security flags: `allowEval`, `allowScriptTags`, `selfRequestsOnly`, nonces.
- [ ] Define a `responseHandling` policy for error/validation status codes.
- [ ] Ensure the server branches on the `HX-Request` header (fragment vs. full page).
- [ ] Add `hx-history="false"` to any page caching sensitive data in `sessionStorage`.
- [ ] Set a `timeout` if your backend can hang, and handle `htmx:timeout`.
- [ ] Establish a CSP and verify htmx features still work under it.

> [!WARNING]
> If you serve htmx from a CDN **without** Subresource Integrity, a compromise of that CDN would let an attacker run arbitrary JavaScript on every page that includes htmx. SRI hashes (which the `npm run sha` script helps generate) pin the exact bytes you trust.

### 8.3 Performance characteristics

- htmx adds work proportional to the number of `hx-*`-annotated elements processed on load and after each swap. It caches attribute hashes to skip unchanged nodes.
- Trigger spec parsing can be cached via `htmx.config.triggerSpecsCache` if you have many elements sharing identical `hx-trigger` strings.
- The library uses event delegation and `XMLHttpRequest`; there is no virtual DOM diffing overhead. The cost model is "parse a fragment, do native DOM insertion, settle a few attributes."

---

## 9. Editor & Tooling Integration

`editors/jetbrains/htmx.web-types.json` provides [Web-Types](https://github.com/JetBrains/web-types) metadata so JetBrains IDEs (WebStorm, IntelliJ, PyCharm) offer autocomplete and documentation for `hx-*` attributes. It is generated by `scripts/generate-web-types.mjs` and published as the package's `web-types` field, so consumers who `npm install htmx.org` get editor support automatically. For VS Code, equivalent support is provided by community extensions consuming the same metadata.

---

## 10. Extending htmx

Extensions are first-class. Register one with `htmx.defineExtension(name, partialExtension)`; activate it on a subtree with `hx-ext="name"`.

The extension interface (defaults shown by `extensionBase()`):

| Hook | Signature | Purpose |
|------|-----------|---------|
| `init(api)` | `(internalAPI) => void` | Receive the internal API on registration |
| `onEvent(name, evt)` | `(string, CustomEvent) => boolean` | Observe/veto any htmx event (`return false` to cancel) |
| `transformResponse(text, xhr, elt)` | `=> string` | Rewrite the raw response before swapping |
| `isInlineSwap(swapStyle)` | `=> boolean` | Declare a custom swap style as inline |
| `handleSwap(swapStyle, target, fragment, settleInfo)` | `=> boolean \| Node[]` | Implement a custom swap style (e.g. morph) |
| `encodeParameters(xhr, params, elt)` | `=> any \| string \| null` | Custom request body encoding |
| `getSelectors()` | `=> string[] \| null` | Extra selectors htmx should process |

In htmx 2, formerly-core features such as **WebSockets** (`ws`), **Server-Sent Events** (`sse`), and class-based response targeting moved into official extensions, keeping the core library small.

```javascript
htmx.defineExtension('uppercase', {
  transformResponse: function (text /*, xhr, elt */) {
    return text.toUpperCase()
  }
})
```

---

## 11. Code Quality & Conventions

| Observation | Detail |
|-------------|--------|
| Single-file design | The whole library is one IIFE in `src/htmx.js`. This is intentional — it keeps the distributable a single small file and avoids a bundler. |
| Defensive DOM access | Helpers like `asElement`, `asHtmlElement`, `asParentNode`, and `getInternalData` guard against null/wrong-type nodes throughout. |
| Type safety via JSDoc | Extensive JSDoc `@param`/`@returns`/`@typedef` annotations drive `tsc --checkJs` and the generated `.d.ts`. |
| Terse style | Many lint rules are relaxed; the code favors compactness. Contributors should match the surrounding style and run `npm run format`. |
| Shadow DOM aware | `getRootNode({composed})`, `parentElt` (handles `ShadowRoot`), and extended selectors (`host`, `root`) show deliberate shadow-DOM support. |
| Browser-compat workarounds | E.g. `normalizeScriptTags` re-creates `<script>` nodes because some browsers won't execute scripts inserted from `<template>` content. |

---

## 12. Potential Improvements & Technical Debt

These are observations for maintainers and downstream forks, derived from the source and issue tracker — not defects in normal operation.

- **Single-file size:** at ~5,600 lines the library is large for one file. Internal modularization (kept bundled for distribution) could improve contributor onboarding, at the cost of a build step the project currently avoids by design.
- **`eval` surface:** `allowEval`-gated features (`hx-vars`, trigger filters, `js:` vals) are a recurring CSP friction point. Documenting a fully eval-free usage path prominently would help locked-down deployments.
- **OOB-swap nesting (#3743):** the interaction between `allowNestedOobSwaps` and nested OOB elements has a reported surprising case worth a regression test and doc clarification.
- **Indicator lifecycle on redirect (#3189):** `hx-indicator` not deactivating on `HX-Redirect` is a UX rough edge.
- **Release artifact consistency (#3765, #3553):** community reports of CDN/GitHub release artifact mismatches suggest the release pipeline (`scripts/`) could benefit from tighter automation/verification.

---

## 13. Quick Reference

### Security flags at a glance

| Flag | Harden by setting |
|------|-------------------|
| `allowEval` | `false` (strict CSP, no `unsafe-eval`) |
| `allowScriptTags` | `false` (strip scripts from swapped HTML) |
| `selfRequestsOnly` | keep `true` (default) |
| `withCredentials` | keep `false` unless cross-site auth is required |
| `inlineScriptNonce` / `inlineStyleNonce` | your per-request CSP nonce |

### Request headers htmx sends

`HX-Request`, `HX-Trigger`, `HX-Trigger-Name`, `HX-Target`, `HX-Current-URL`, `HX-Prompt`, `HX-Boosted`, `HX-History-Restore-Request`

### Response headers htmx honors

`HX-Location`, `HX-Redirect`, `HX-Refresh`, `HX-Push-Url`, `HX-Replace-Url`, `HX-Retarget`, `HX-Reswap`, `HX-Reselect`, `HX-Trigger`, `HX-Trigger-After-Swap`, `HX-Trigger-After-Settle`

### Build/test commands

```bash
npm install           # install dev toolchain
npm run dist          # build dist/ + types + web-types
npm test              # lint + type-check + chrome tests
npm run test:ci       # full gate: lint + type-check + all browsers
npm run www           # build/serve the docs site
```

---

_Generated June 2026 from bigskysoftware/htmx at 5e867de. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
