Skip to content

URL tag helper

Hard-coded URLs like hx-get="/products/7" break when routes change. HtmxUrlTagHelper brings ASP.NET Core’s routing to htmx: you give it a page, controller/action, area or named route and it writes the generated URL into the htmx verb attribute you put on the element. It behaves like the built-in anchor tag helper, but for hx-get, hx-post, hx-put, hx-patch and hx-delete.

samples/Htmx4.Sample/Pages/UrlHelper.cshtml
<button hx-get hxn-page="/UrlHelper" hxn-page-handler="Fragment" hx-target="#url-result-box">Page</button>
<button hx-get hxn-controller="Demo" hxn-action="Echo" hxn-route-id="7" hx-target="#url-result-box">MVC</button>
<button hx-get hxn-route="demo-echo" hxn-route-id="42" hxn-route-p="rv" hx-target="#url-result-box">Named route</button>
<button hx-get hxn-area="Admin" hxn-controller="Admin" hxn-action="Ping" hx-target="#url-result-box">Area</button>
<button hx-post hxn-page="/UrlHelper" hxn-page-handler="Post" hx-target="#url-result-box">POST</button>

Rendered output (example):

<button hx-get="/UrlHelper?handler=Fragment" hx-target="#url-result-box">Page</button>
<button hx-get="/demo/echo/7" hx-target="#url-result-box">MVC</button>

The matching handlers:

UrlHelper.cshtml.cs
public IActionResult OnGetFragment() => Content("page handler Fragment", "text/html");
public IActionResult OnPostPost() => Content("page handler Post ok", "text/html");
// DemoController.cs
[HttpGet("echo/{id?}", Name = "demo-echo")]
public IActionResult Echo(string? id, string? p) => /* ... */;
Attribute Property Purpose
hxn-page Page Razor Page path, e.g. /Products/Index
hxn-page-handler PageHandler Page handler name (OnGetFragment -> Fragment)
hxn-controller Controller MVC controller name
hxn-action Action MVC action name
hxn-area Area MVC area (also valid with pages)
hxn-route Route Named route
hxn-route-{key} RouteValues Route/query values: hxn-route-id="7" -> id=7. Case-insensitive dictionary.
hxn-host Host Makes the URL absolute with this host
hxn-protocol Protocol Scheme for an absolute URL (https)
hxn-fragment Fragment URL #fragment

Other members: Generator (the injected IHtmlGenerator), ViewContext (injected), Order (-1000, so it runs before other tag helpers) and Process(...). The helper only runs when hxn-page, hxn-action, hxn-controller or hxn-route is present.

  • Put exactly one htmx verb on the element. More than one throws InvalidOperationException.
  • Pick one link style: route, action/controller, or page/handler. Mixing them throws InvalidOperationException.
  • An existing non-empty verb value wins; the helper does nothing.
  • Binding attributes with no verb on the element do nothing (there is nowhere to write the URL).

The helper recognizes hx-get, hx-get:inherited, data-hx-get, data-hx-get:inherited (and likewise for post, put, patch, delete) and writes the URL into whichever spelling is present.

htmx 4 turned implicit inheritance off. A child sees a parent’s attribute only when the parent writes it with :inherited (or htmx.config.implicitInheritance is true).

samples/Htmx4.Sample/Pages/Inherited.cshtml
<section hx-target:inherited="#inh-out" hx-headers-X-Demo="from-parent" hx-headers-inherited="true">
<button hx-get="/demo/inherited/echo?p=inh">child uses parent's target and headers</button>
</section>
Attribute Inherits with :inherited?
hx-target, hx-swap, hx-headers, hx-include, … Yes
hx-get, hx-post, hx-put, hx-patch, hx-delete No: they do not make descendants send requests
<section hx-target:inherited="#out" hx-headers-X-Tenant="@tenant" hx-headers-inherited="true">
<button hx-get hxn-controller="Orders" hxn-action="List">Orders</button>
<button hx-get hxn-controller="Invoices" hxn-action="List">Invoices</button>
</section>
<div id="out"></div>

The server can verify what the child inherited through request detection: h.TargetElement reports div#out, and Request.Headers["X-Tenant"] carries the header. See the Headers tag helper.

htmx 4 serializes DELETE data into the query string and does not include enclosing form inputs automatically. Add hx-include when you need them:

<form>
<input name="id" value="7" />
<button hx-delete hxn-page="/ErrorSwaps" hxn-page-handler="Delete"
hx-include="closest form" hx-target="#del-out">Delete</button>
</form>

For antiforgery on DELETE see the Antiforgery guide.


See the live demos: run dotnet run --project samples/Htmx4.Sample and open /UrlHelper (source) and /Inherited (source).

Related: Headers tag helper, Config tag helper, htmx 4 reference: attribute model.

From the blog: HTMX With ASP.NET Core covers the URL tag helpers alongside the rest of the library.