Tag Helpers
HtmxToolkit Tag Helpers generate URLs and JSON attributes through ASP.NET Core routing and Razor encoding.
They complement ordinary HTMX attributes such as hx-target, hx-trigger, and hx-swap.
Register the namespace and Tag Helpers in _ViewImports.cshtml:
@using Ramstack.HtmxToolkit
@addTagHelper *, Ramstack.HtmxToolkit
Generate request URLs
Razor Pages
<button hx-page="/Orders/Details"
hx-page-handler="Status"
hx-route-id="@Model.OrderId"
hx-target="#order-status">
Refresh status
</button>
This generates hx-get because no HTMX method attribute was supplied.
Add an empty hx-post, hx-put, hx-patch, or hx-delete to select another method:
<button hx-post
hx-page="/Orders/Edit"
hx-page-handler="Archive"
hx-route-id="@Model.OrderId">
Archive
</button>
MVC controllers
<button hx-post
hx-area="Admin"
hx-controller="Users"
hx-action="Disable"
hx-route-id="@user.Id"
hx-target="#user-@user.Id">
Disable
</button>
Named routes and route dictionaries
Name an endpoint, then use that name in hx-route. For example, replace the [HttpGet] attribute on
the Details action in the MVC tutorial with:
[HttpGet("/products/{id:int}", Name = "product-details")]
public IActionResult Details(int id)
{
if (id != 42)
return NotFound();
return PartialView("_ProductDetails", _products.Current);
}
The tutorial's MapDefaultControllerRoute() also maps this attribute-routed action.
In Views/Products/Index.cshtml, use the route name instead of controller and action attributes:
<button hx-route="product-details"
hx-route-id="42"
hx-target="#product-panel">
Product details
</button>
<div id="product-panel"></div>
The generated URL is /products/42. Use hx-all-route-data when route values are already in a dictionary:
@{
var routeValues = new Dictionary<string, string>
{
["id"] = "42",
["tab"] = "reviews"
};
}
<button hx-route="product-details"
hx-all-route-data="routeValues"
hx-target="#product-panel">
Product details
</button>
Here id fills the route parameter and tab becomes a query parameter: /products/42?tab=reviews.
The action can accept a string? tab parameter if it needs that value.
Use hx-route-* for individual values or hx-all-route-data for the dictionary.
hx-fragment works like ASP.NET Core's asp-fragment: hx-fragment="reviews" appends #reviews
to the generated URL. It is a Tag Helper input, not an HTMX attribute. The browser does not send the fragment
to the server, so use hx-target, not the fragment, to choose the element to update.
Choose one routing mode: hx-route, controller/action, or page/handler.
Combining modes throws an InvalidOperationException.
Supplying more than one HTMX method is also invalid.
Send additional values
Use hx-val-* when fixed values should be included as request parameters:
<button hx-page="/Reports"
hx-page-handler="Preview"
hx-val-category="science"
hx-val-format="summary"
hx-target="#preview">
Preview
</button>
The Tag Helper emits an encoded hx-vals JSON object. A dictionary can be supplied through hx-all-vals:
<button hx-get="/reports/preview"
hx-all-vals="@Model.PreviewValues">
Preview
</button>
HTMX 1.x and 2.x inherit the generated hx-vals from parent elements automatically.
For HTMX 4.x, add hx-vals-inherited="true" to the Razor element to generate the explicit inheritance modifier:
<section hx-vals-inherited="true"
hx-val-tenant="@Model.TenantId">
...
</section>
This produces hx-vals:inherited='{"tenant":"..."}'.
HTMX 1.x and 2.x merge inherited values: a child value overrides a value with the same name
while other inherited values remain. In HTMX 4.x, a plain child hx-vals replaces the inherited
object entirely. Add hx-vals-append="true" to merge the child object instead:
<section hx-vals-inherited="true"
hx-val-tenant="@Model.TenantId">
<button hx-post="/reports/preview"
hx-vals-append="true"
hx-val-format="summary">
Preview
</button>
</section>
The button emits hx-vals:append='{"format":"summary"}'.
Send custom headers
<section hx-header-X-Tenant="@Model.TenantId">
<button hx-get="/reports" hx-target="#report">Load report</button>
</section>
hx-header-* and hx-all-headers generate hx-headers JSON. Header names are compared without case.
HTMX 1.x and 2.x inherit the generated attribute automatically.
For HTMX 4.x, use the Toolkit input hx-headers-inherited="true" on a parent element:
<section hx-headers-inherited="true"
hx-header-X-Tenant="@Model.TenantId">
...
</section>
This generates hx-headers:inherited.
Use hx-headers-append="true" on a child declaration to merge its headers into the inherited object.
Do not use custom client headers as proof of identity or authorization; clients can modify them. Authenticate and authorize on the server.
Configure one request
Use hx-request-* attributes rather than hand-writing version-specific JSON:
<button hx-page="/Reports"
hx-page-handler="Preview"
hx-request-timeout="2000"
hx-request-credentials="@HtmxRequestCredentials.SameOrigin"
hx-target="#preview">
Generate preview
</button>
The output depends on the configured HTMX target:
<!-- HTMX 1.x and 2.x -->
<button hx-request='{"timeout":2000,"credentials":false}' ...>
<!-- HTMX 4.x -->
<button hx-config='{"timeout":2000,"credentials":"same-origin"}' ...>
| Razor attribute | V1/V2 | V4 |
|---|---|---|
hx-request-timeout |
Yes | Yes |
hx-request-credentials |
Boolean mapping | Fetch credentials mode |
hx-request-no-headers |
Yes | No |
hx-request-cache |
No | Yes |
hx-request-redirect |
No | Yes |
hx-request-referrer |
No | Yes |
hx-request-integrity |
No | Yes |
hx-request-validate |
No | Yes |
Unsupported properties are omitted from the generated JSON. In particular, HtmxRequestCredentials.Omit
cannot be represented by HTMX 1.x or 2.x and is omitted for those targets.
Use HtmxRequestCache and HtmxRequestRedirect for the HTMX 4.x Fetch options:
<button hx-get="/Reports/Preview"
hx-request-cache="@HtmxRequestCache.NoCache"
hx-request-redirect="@HtmxRequestRedirect.Error">
Generate preview
</button>
This generates hx-config='{"cache":"no-cache","redirect":"error"}'.
Leaving either property unset omits that option from the JSON.
HtmxRequestCache.OnlyIfCached requires the same-origin Fetch mode, which is the HTMX default;
the request fails if HtmxV4Config.Mode specifies another mode.
Apply attribute modifiers
HTMX 4.x requires attribute inheritance to be explicit by default. Razor cannot use the colon-form HTMX modifier as a bound Tag Helper input, so HtmxToolkit provides hyphenated boolean inputs and generates the correct client attribute:
| Razor Tag Helper input | Generated HTMX 4 attribute |
|---|---|
hx-request-inherited="true" |
hx-config:inherited |
hx-request-append="true" |
hx-config:append |
hx-vals-inherited="true" |
hx-vals:inherited |
hx-vals-append="true" |
hx-vals:append |
hx-headers-inherited="true" |
hx-headers:inherited |
hx-headers-append="true" |
hx-headers:append |
For example, inherit per-request timeout configuration from a parent:
<section hx-request-inherited="true"
hx-request-timeout="2000">
...
</section>
Use inherited on a parent declaration to make it available to descendants, and append on a child
declaration to merge rather than replace the inherited object. When both inputs are true on the same
element, HtmxToolkit emits one combined attribute such as hx-config:inherited:append.
For HTMX 1.x and 2.x, these boolean inputs do not change the generated names.
hx-request, hx-vals, and hx-headers already inherit from parents: child values override matching
parent values and keep the rest.
Alternatively, set HtmxV4Config.ImplicitInheritance to true to enable inheritance globally;
the *-inherited inputs are then unnecessary. Child declarations can still use the corresponding
*-append input when they need to merge with inherited values.
Render global configuration
<htmx-config /> renders the version-specific application configuration and antiforgery metadata:
<head>
<htmx-config />
</head>
The equivalent <meta htmx-config /> form is also supported. Prefer the dedicated element for readability.
See Application configuration and Antiforgery and Toolkit script for the required layout setup.
Refer to the API reference for every property and accepted type.