HtmxToolkit integrates HTMX with ASP.NET Core. It provides strongly typed APIs for request and response headers, MVC action filters, Razor Tag Helpers, application-wide HTMX configuration, and antiforgery support.
- Supports .NET 6 or later.
- Supports HTMX 1.9.x, HTMX 2.x, and HTMX 4.x. HTMX 2.x is selected by default.
See the documentation for full guides and recipes.
- Detect HTMX requests, including boosted requests, without comparing header strings.
- Read and write all standard HTMX headers through strongly typed APIs.
- Route HTMX requests to dedicated MVC actions with
[HtmxRequest]. - Configure response behavior fluently or with
[HtmxResponse]. - Generate HTMX URLs, headers, values, and request options with Razor Tag Helpers.
- Render version-specific HTMX configuration from ASP.NET Core options.
- Add antiforgery tokens to non-GET HTMX requests with a small companion script.
HtmxRequestHeaders and HtmxResponseHeaders are readonly structs, each containing a single reference.
This avoids allocating wrapper objects and allows the JIT to optimize away the wrapper overhead in inlined code.
Version-specific configuration JSON is cached and reused until the configuration changes.
Known JSON shapes use source-generated System.Text.Json metadata, avoiding reflection-based metadata discovery at runtime.
dotnet add package Ramstack.HtmxToolkitRegister HtmxToolkit in Program.cs:
using Ramstack.HtmxToolkit.Hosting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHtmxToolkit();Important
HtmxToolkit does not bundle HTMX itself. Add a supported HTMX release to the application separately.
Make the Tag Helpers and toolkit types available to Razor views in _ViewImports.cshtml:
@using Ramstack.HtmxToolkit
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Ramstack.HtmxToolkitRender the configuration metadata in the document <head>:
<head>
<htmx-config />
</head>On ASP.NET Core 9 or later, enable static assets and associate them with the endpoints that render views in Program.cs:
app.MapStaticAssets();
app.MapRazorPages().WithStaticAssets();Note
For MVC, apply .WithStaticAssets() to each controller endpoint builder that renders views
On ASP.NET Core 6–8, enable static files:
app.UseStaticFiles();The NuGet package includes the toolkit script as a static web asset. Load it after HTMX in the layout:
<script src="/path/to/htmx.min.js"></script>
<script src="~/_content/Ramstack.HtmxToolkit/htmx-toolkit.min.js"
asp-append-version="true"></script>You can now generate an HTMX URL from ASP.NET Core route information:
<button hx-get
hx-controller="Books"
hx-action="List"
hx-route-category="science"
hx-target="#results">
Browse books
</button>
<div id="results"></div>- Use
hx-post,hx-put,hx-patch, orhx-deleteinstead ofhx-getto select another HTTP method. - If the method attribute is omitted, the URL Tag Helper defaults to
hx-get.
Use IsHtmxRequest when an endpoint should return a partial response for an HTMX request and a full-page response otherwise:
public IActionResult Help()
{
if (Request.IsHtmxRequest(out var htmx) && !htmx.HistoryRestoreRequest)
return PartialView("_Help");
return View();
}Checking HistoryRestoreRequest ensures that an HTMX history cache miss receives the full page it expects.
The out parameter provides strongly typed access to the request headers.
Call Request.GetHtmxHeaders() to access the same headers separately from request detection.
Available headers and their formats vary by HTMX version; see the request header comparison.
HtmxRequestHeaderNames exposes the corresponding header-name constants for lower-level APIs.
Use Request.IsHtmxBoosted() when only boosted navigation matters.
An overload also provides access to the strongly typed headers.
Apply [HtmxRequest] to reserve an action for HTMX requests:
[HttpGet("/profile/fragment")]
[HtmxRequest]
public IActionResult ProfileFragment()
{
return PartialView("_Profile");
}Set Boosted to restrict action selection to boosted or non-boosted HTMX requests:
[HtmxRequest(Boosted = true)]
public IActionResult BoostedNavigation()
{
return PartialView("_Navigation");
}Configure HTMX response headers through Response.Htmx(...):
Response.Htmx(htmx => htmx
.Retarget("#profile")
.Reswap(HtmxSwap.OuterHtml)
.TriggerEvent("profile-updated"));Note
The callback runs only for HTMX requests, so non-HTMX requests avoid unnecessary response work.
The fluent API supports:
- Client navigation with
Location,Redirect,PushUrl, andReplaceUrl. - Swap control with
Reswap,Retarget, andReselect. - Page refresh with
Refresh. - Client events with
TriggerEvent.
The same API works with any HttpResponse, including
Minimal API handlers.
Use state-passing overloads to avoid closure allocations in hot paths.
For trimming and Native AOT, pass source-generated JsonTypeInfo<T> metadata to TriggerEvent;
see the complete example.
Call Response.GetHtmxHeaders() for direct access to the strongly typed response headers,
or use HtmxResponseHeaderNames with lower-level APIs.
Controllers can set common response headers declaratively:
[HtmxRequest]
[HtmxResponse(
Retarget = "#results",
Reswap = HtmxSwap.BeforeEnd)]
public IActionResult LoadMore()
{
return PartialView("_MoreResults");
}HtmxResponseAttribute supports Refresh, Reswap, ReswapExpression, Retarget, and Reselect.
Use ReswapExpression for a complete expression with swap modifiers, such as innerHTML show:#result:top.
HtmxToolkit includes five Tag Helpers:
| Tag Helper | Purpose |
|---|---|
HtmxUrlTagHelper |
Builds HTMX request URLs from routes, controllers, actions, or Razor Pages. |
HtmxHeaderTagHelper |
Serializes custom hx-headers values. |
HtmxValsTagHelper |
Serializes additional hx-vals request values. |
HtmxRequestTagHelper |
Generates version-specific hx-request or hx-config options. |
HtmxConfigTagHelper |
Renders application configuration and antiforgery metadata. |
Controller and action:
<button hx-post
hx-area="Admin"
hx-controller="Users"
hx-action="Disable"
hx-route-id="@Model.Id">
Disable user
</button>Razor Page handler:
<button hx-page="/Attendee"
hx-page-handler="Profile"
hx-route-attendeeid="@Model.Id">
Show profile
</button>Use hx-all-route-data for an IDictionary<string, string> of route values.
The helper also supports hx-route, hx-host, hx-protocol, and hx-fragment.
Create hx-headers without manually escaping JSON:
<button hx-get="/reports"
hx-header-X-View="compact"
hx-header-X-Time-Zone="UTC">
Load report
</button>Add request values in the same way:
<button hx-get="/books"
hx-val-category="science"
hx-val-format="summary">
Browse books
</button>Use hx-all-headers or hx-all-vals to supply an IDictionary<string, string>.
For HTMX 1.9.x and 2.x, typed hx-request-* attributes generate hx-request JSON:
<button hx-get="/reports"
hx-request-timeout="5000"
hx-request-credentials="@HtmxRequestCredentials.Include"
hx-request-no-headers="false">
Load report
</button>With HTMX 4.x selected, HtmxRequestTagHelper generates hx-config instead.
See Version compatibility for supported options.
HTMX 1.9.x and 2.x merge-inherit hx-request, hx-vals, and hx-headers automatically.
With HTMX 4.x, use the Tag Helper's inherited input on a parent and append on a child to merge their values:
<div hx-vals-inherited="true"
hx-val-category="books">
<button hx-post="/search"
hx-vals-append="true"
hx-val-sort="title">
Search
</button>
</div>The Tag Helper emits hx-vals:inherited on the parent and hx-vals:append on the child.
See Attribute modifiers for all supported inputs and version differences.
Configure HTMX once during service registration. Only explicitly configured values are emitted, so HTMX defaults remain in effect:
builder.Services.AddHtmxToolkit(options =>
{
options.UseHtmxV2(config =>
{
config.ReportValidityOfForms = true;
config.DefaultFocusScroll = true;
});
});Select the version loaded by the browser with UseHtmxV1, UseHtmxV2, or UseHtmxV4:
builder.Services.AddHtmxToolkit(options => options.UseHtmxV4());Warning
Select only one HTMX version. Attempting to select a second version in the same configuration throws an exception.
See the configuration guides for version-specific settings and response handling.
Antiforgery metadata is enabled by default. <htmx-config /> renders the current token and field or header names;
the companion script attaches the token to non-GET HTMX requests and refreshes it after boosted navigation.
Warning
The companion script sends the token but does not validate it. Razor Pages validates unsafe HTTP methods automatically. MVC applications must enable server-side antiforgery validation for the relevant actions.
For example, MVC applications can validate all unsafe actions globally:
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute());
});Disable the metadata when antiforgery is handled elsewhere:
builder.Services.AddHtmxToolkit(options =>
{
options.IncludeAntiforgeryToken = false;
});See Antiforgery and Toolkit script for setup and script-loading details.
Important
HTMX 1.9.x and 2.x support HX-Trigger, HX-Trigger-After-Swap, and HX-Trigger-After-Settle.
HTMX 4.x supports only HX-Trigger, which fires when the request completes (after the swap whenever one is performed).
HtmxToolkit therefore emits events requested for any HtmxTriggerTiming
value through that header rather than dropping them. The Receive and AfterSettle timings cannot be preserved exactly.
HtmxSwap.InnerMorph and HtmxSwap.OuterMorph use the native innerMorph and outerMorph swap styles in HTMX 4.x.
No additional client-side dependency or configuration is required.
With HTMX 1.9.x or 2.x, enable the toolkit's ramstack-morph extension and load Idiomorph for morphing:
<body hx-ext="ramstack-morph">
...
</body>Without Idiomorph, the extension falls back to HTML replacement. See Morph swaps for script setup, supported styles, and fallback behavior.
The samples/Ramstack.HtmxToolkit.Demo project demonstrates request detection,
response headers and events, MVC attributes, Tag Helpers, polling, boosted navigation, and antiforgery integration.
Run it from the repository root:
dotnet run --project samples/Ramstack.HtmxToolkit.DemoThe application is available at https://localhost:5001 and http://localhost:5000.
See Building the documentation locally for build and preview commands.
Bug reports and pull requests are welcome. To validate a change locally:
dotnet build
dotnet test| Version | |
|---|---|
| .NET | 6, 7, 8, 9, 10, 11 |
| HTMX | 1.9.x, 2.x, 4.x |
HtmxToolkit is available under the MIT License.