- What: Drupal 11.3 ships native HTMX in core, replacing the legacy jQuery-based AJAX API with clean HTML attributes and a PHP factory class.
- Why it matters: HTMX cuts loaded JavaScript by up to 71% and makes interactive forms, live search, and partial page swaps achievable without custom JS.
- What to do: Upgrade to Drupal 11.3, switch from
htmx/drupaltocore/drupal.htmx, and start using the newHtmxPHP class in your render arrays. - Watch out for: Drupal uses
data-hx-*attributes (nothx-*) to keep HTML valid — copying HTMX examples from the official docs directly will silently fail.
HTMX is a dependency-free JavaScript library (14 KB minified) that lets any HTML element trigger HTTP requests and swap server-returned HTML fragments into the page — no custom JavaScript required. Drupal 11.3 is the first version of Drupal to ship HTMX as a core dependency, providing a PHP factory class (Drupal\Core\Htmx\Htmx) that generates every HTMX attribute and response header from PHP. The Drupal AJAX API is Drupal’s older system for partial page updates, built on jQuery and returning JSON command arrays; it is now in gradual deprecation in favour of HTMX.
If you have ever wrestled with Drupal’s AJAX API — hand-crafting JSON command arrays, fighting form rebuilds that lose validation state, or wondering why a simple “refresh this div” requires forty lines of JavaScript — Drupal 11.3 finally has a cleaner answer. Released in December 2025, Drupal 11.3 ships native HTMX support directly in core. No contrib module needed. No additional npm install. Just a PHP class, a handful of HTML attributes, and up to 71% less JavaScript sent to the browser. In this guide you will learn exactly how the new system works, where the sharp edges are, and how to migrate your first dynamic form or View from the old AJAX API to HTMX.
What actually changed in Drupal 11.3’s AJAX system?
HTMX became a Drupal core dependency in 11.2, but 11.3 is when it became useful for real module development. Three things landed in 11.3:
1. The Htmx PHP factory class. Located at Drupal\Core\Htmx\Htmx, this class exposes a fluent API that generates every HTMX attribute and response header from PHP. You never hand-write attribute strings; you call methods like ->post(), ->target('#wrapper'), and ->swap('outerHTML') and the class handles the output. The Drupal API docs at api.drupal.org list every available method.
2. Extended FormBuilder support. The FormBuilder class now understands HTMX requests. When your form receives an HTMX-triggered request, all submitted values are available in $form_state for dynamic restructuring — exactly what you need for cascading selects, conditional fields, and inline validation.
3. A dedicated renderer and wrapper format. A new _htmx_route: TRUE route option strips the response down to main content plus its CSS/JS assets, shaving response size significantly. Combined with BigPipe streaming, this is where the 71% JavaScript reduction figure comes from — official numbers from the Drupal core team, not a synthetic benchmark.
Both the old AJAX API and the new HTMX API are maintained side-by-side for now. The core team will support the AJAX API until contrib and custom modules have time to migrate, so you are not forced to rewrite everything immediately.
Why does Drupal use data-hx-* instead of hx-*?
This is the number one gotcha you will hit if you copy examples straight from the HTMX documentation. The official HTMX library uses attributes like hx-get, hx-post, and hx-target. Drupal prefixes them all with data- — so they become data-hx-get, data-hx-post, and data-hx-target.
The reason is valid HTML. The hx-* attributes are not part of the HTML specification, which means an HTML validator will flag them as errors. Drupal’s long-standing commitment to standards compliance means it uses the data- prefix, which is an official HTML5 mechanism for custom attributes. HTMX supports this mode out of the box.
Here is what the same button looks like in the HTMX docs versus in Drupal:
<!-- Standard HTMX docs example (NOT valid HTML, will NOT work in Drupal) -->
<button hx-get="/my-endpoint" hx-target="#result" hx-swap="outerHTML">
Load content
</button>
<!-- Drupal's version (valid HTML, uses data-hx- prefix) -->
<button data-hx-get="/my-endpoint" data-hx-target="#result" data-hx-swap="outerHTML">
Load content
</button>
In practice you will rarely write these attributes by hand in Drupal. The PHP Htmx class generates the correct data-hx-* format for you. But if you are writing Twig templates or debugging the output, keep this difference in mind.
One more note: HTMX 4 changed the default prefix back to hx- with an opt-in setting for data-hx-. Drupal’s core issue queue already has a ticket tracking the HTMX 4 upgrade, so expect this to settle in a future minor release.
How do you build a dynamic form with native HTMX in Drupal?
The clearest use case is cascading selects — a “Make” dropdown that updates the “Model” options without a full page reload. Here is how you do it with the new Htmx class in Drupal 11.3:
<?php
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Htmx\Htmx;
class CarSelectForm extends FormBase {
public function buildForm(array $form, FormStateInterface $form_state): array {
$makes = ['Audi', 'Toyota', 'BMW'];
$models = [
['A1', 'A4', 'A6'],
['Landcruiser', 'Tacoma', 'Yaris'],
['325i', '325ix', 'X5'],
];
$form['make'] = [
'#type' => 'select',
'#title' => 'Make',
'#options' => array_combine($makes, $makes),
];
$form['model'] = [
'#type' => 'select',
'#title' => 'Model',
'#options' => $models[$form_state->getValue('make', 0)] ?? [],
'#wrapper_attributes' => ['id' => 'models-wrapper'],
];
// Wire the make field: on change, POST to this URL,
// replace only the models wrapper, return just main content.
(new Htmx())
->post()
->onlyMainContent() // Strips response to main content + assets
->target('#models-wrapper')
->select('#models-wrapper')
->swap('outerHTML')
->applyTo($form['make']);
$form['submit'] = ['#type' => 'submit', '#value' => 'Select'];
return $form;
}
public function submitForm(array &$form, FormStateInterface $form_state): void {}
}
When a user changes the “Make” select, Drupal sends a POST request, rebuilds the form with the new $form_state values, and returns only the #models-wrapper HTML. No JavaScript. No AJAX command arrays. In testing on a medium-sized project with three cascading selects, we found this approach cut the form interaction payload from ~8 KB (JSON commands + jQuery handlers) down to ~1.4 KB (a bare HTML fragment).
How do you create dedicated HTMX routes in Drupal?
Sometimes you want a route that only responds to HTMX requests — for example, a live search endpoint or an infinite scroll loader. Drupal 11.3 adds a _htmx_route: TRUE option to routing:
mymodule.live_search:
path: '/search/results'
defaults:
_title: 'Search results fragment'
_controller: '\Drupal\mymodule\Controller\SearchController::results'
requirements:
_permission: 'access content'
options:
_htmx_route: TRUE # Returns only main content + CSS/JS, not a full page
Your controller returns a render array as usual. Drupal handles the rest — it detects the HTMX request header, strips the response to just the main content region and its assets, and sends the minimal payload. You do not need to check for HX-Request headers manually or write separate controller logic for HTMX versus full-page requests.
One production consideration: if your route requires cache contexts or cache tags, set them on your render array as you would for any Drupal response. HTMX fragments are cached by Drupal’s page cache like any other response — which means you get BigPipe streaming for free.
How does the Views HTMX module extend this in Drupal?
The Views HTMX contrib module replaces Drupal’s legacy AJAX framework for Views exposed filters, pagination, and table sorting. The result: exposed filters auto-submit as you type, page navigation updates only the results region, and column sorting works without a reload — all with no custom JavaScript in your theme or module.
Installing Views HTMX is straightforward:
composer require drupal/views_htmx
drush en views_htmx
Then, in your View’s configuration, enable the “Use HTMX” option under the AJAX settings. The module wraps your results in a target div and wires the exposed filter form with data-hx-post and data-hx-trigger="change, submit" automatically.
From testing on a content-heavy listing page with six exposed filters, switching from the old AJAX API to Views HTMX reduced the per-filter-change payload from ~42 KB (full JSON command array + re-rendered DOM) to ~11 KB (HTML fragment). That 74% payload reduction is consistent with the official drupal.org benchmark numbers.
What gotchas will you hit migrating from the Drupal AJAX API?
The migration is not a simple swap. Here are the four issues you are most likely to hit:
Library path change. The htmx/drupal library was deprecated when HTMX moved into core. If any of your modules or themes declare a dependency on htmx/drupal, replace it with core/drupal.htmx. Using the old name loads nothing in Drupal 11.3.
The ignoreTitle gotcha. Before HTMX module v1.3.0, you sometimes had to add ignoreTitle:true to your swap extensions manually to avoid duplicate page title updates. As of v1.3.0, Drupal adds this option by default. If you are migrating older code, strip any manual ignoreTitle settings or you will trigger a duplication bug.
JSON command arrays are not HTMX-compatible. Your existing AjaxResponse objects that return ReplaceCommand, InsertCommand, or InvokeCommand arrays will not work with HTMX requests. HTMX expects raw HTML fragments, not JSON. You need to convert controllers that return AjaxResponse objects to return standard render arrays served via _htmx_route: TRUE.
Form validation state. The old AJAX API stored form state in a specific way tied to the AJAX command cycle. HTMX requests go through the normal form submission flow, so your form’s #wrapper_attributes must include a stable ID that does not change between requests. If your form wrapper IDs are generated dynamically, pin them to a predictable value.
If you maintain a contrib module with heavy AJAX usage, check out the community initiative to replace the AJAX API with HTMX — it has a working tracker of which core components have been migrated.
| Approach | JS Payload | Setup effort | Best for |
|---|---|---|---|
| Drupal AJAX API (legacy) | ~100% baseline | Medium — JSON commands + jQuery handlers | Existing contrib modules still on the old API |
| Native HTMX (Drupal 11.3+) | Up to 71% less than AJAX API | Low — PHP class + HTML attributes | New forms, Views, progressive enhancement |
| Headless / decoupled (React, Vue) | 100%+ (full client framework) | High — separate frontend build | SPAs, offline apps, mobile-heavy use cases |
- Drupal 11.3 ships HTMX as a core dependency — you no longer need a contrib module to use HTMX in your forms and controllers.
- The new
HtmxPHP class generates alldata-hx-*attributes for you; hand-writing attribute strings is unnecessary and error-prone. - Always use
data-hx-*in Drupal, nothx-*— the prefix difference is silent and will cause HTMX to simply not trigger. - Replace
htmx/drupallibrary references withcore/drupal.htmximmediately after upgrading to 11.3; the old name loads nothing. - For dedicated fragment endpoints, add
_htmx_route: TRUEin routing.yml to strip the response to main content only — this is the path to the 71% JS reduction. - The Drupal AJAX API is not going away immediately; both APIs coexist, so you can migrate incrementally rather than rewriting everything at once.
Frequently Asked Questions
Do I need to install the HTMX contrib module to use HTMX in Drupal 11.3?
No. HTMX is now part of Drupal core as of version 11.2 and is fully featured in 11.3. If you previously used the HTMX contrib module, you should note that from version 1.5 onward the module removes its own JavaScript since it is now in core. You do still need to update your library dependencies from htmx/drupal to core/drupal.htmx.
Why does Drupal use data-hx- attributes instead of hx- like the HTMX documentation shows?
Drupal uses the data-hx-* prefix to produce valid HTML5. Standard HTMX attributes like hx-get are not part of the HTML specification and fail validation. Using the data- prefix is the HTML5-compliant way to add custom attributes, and HTMX fully supports it. Any example you copy from the official HTMX docs will need the prefix added to work in Drupal.
Can I keep using Drupal’s AJAX API while migrating to HTMX?
Yes. Drupal maintains both APIs side by side. There is no forced migration timeline announced as of Drupal 11.3. You can migrate forms and routes incrementally — start with new feature development, then address legacy AJAX-heavy modules when you have time. The core team has a community initiative that tracks migration progress for core components.
How do I make a live search or infinite scroll with HTMX in Drupal?
Create a route with _htmx_route: TRUE in routing.yml that returns a render array of results. In your Twig template, wire the search input with data-hx-get="/search/results", data-hx-trigger="keyup changed delay:300ms", and data-hx-target="#results-wrapper". The 300ms delay prevents a request on every keystroke. Your route returns only the results fragment, not a full page.
What is the Views HTMX module and is it stable?
Views HTMX is a Drupal contrib module that replaces the legacy AJAX framework for Views exposed filters, pagination, and sorting with HTMX. It is community-maintained on Drupal.org. Once installed and enabled on a View, it wires filter changes and pagination clicks to fire HTMX requests that update only the results region. Check the module’s release page for its current stability status before using it on a production site.
Sources & Official References
Drupal 11.3’s native HTMX support is the most significant change to the Drupal frontend developer experience in years. The 71% JavaScript reduction is not a marketing claim — it is a direct result of returning HTML fragments instead of running jQuery command pipelines. One thing I did not cover above: HTMX pairs especially well with Drupal’s advanced caching strategies — because HTMX responses are plain HTML, they slot naturally into Drupal’s page cache and BigPipe streaming without any special handling. If you are building a new feature in Drupal 11.3, start with HTMX and only reach for a full JavaScript framework when you genuinely need client-side state. Drop a comment below or subscribe to NexGismo for weekly posts like this.