Getting started
Build a Razor Pages application that loads product details without leaving the page. You will create the page, its handler, and a partial view, then inspect the request in the browser.
This tutorial uses the .NET 10 SDK and HTMX 2.x. The library itself supports .NET 6 or later. You only need basic Razor Pages knowledge; no JavaScript code is required. If HTMX is new to you, read How HTMX updates a page first. For other application styles, see MVC or Minimal APIs.
1. Create the application
Run these commands in the directory where you keep your projects:
dotnet new webapp -n HtmxStarter -f net10.0
cd HtmxStarter
dotnet add package Ramstack.HtmxToolkit
If you already have a Razor Pages application, install the package there and use your own namespace in the examples below.
2. Register the toolkit
Replace Program.cs with:
using Ramstack.HtmxToolkit.Hosting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddHtmxToolkit();
var app = builder.Build();
app.UseStaticFiles();
app.MapRazorPages();
app.Run();
AddHtmxToolkit() selects HTMX 2.x by default. It registers toolkit options; it does not download HTMX.
UseStaticFiles() serves the Toolkit script included in the package.
See Static assets and deployment for MapStaticAssets() and production caching.
3. Enable the Tag Helpers
Replace Pages/_ViewImports.cshtml with:
@using HtmxStarter
@using HtmxStarter.Models
@using Ramstack.HtmxToolkit
@namespace HtmxStarter.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Ramstack.HtmxToolkit
The standard ASP.NET Core helpers resolve ~/ paths and generate versioned script URLs.
The toolkit helpers turn attributes such as hx-page and hx-route-id into HTMX request URLs.
You will add the HtmxStarter.Models namespace in step 5.
4. Load the browser scripts
Replace Pages/Shared/_Layout.cshtml with:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>HtmxStarter</title>
<htmx-config />
</head>
<body>
<main>@RenderBody()</main>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.11/dist/htmx.min.js"
integrity="sha384-2OatzQy1H+Zd/IIrjr1TcuDGqLXeHhbooAyJY1KdQMKnr4LZ22k31GBLdYKHmVjg"
crossorigin="anonymous"></script>
<script src="~/_content/Ramstack.HtmxToolkit/htmx-toolkit.min.js"
asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>
The first script is HTMX, loaded from a CDN. The second is the Toolkit script, served by your application.
Keep that order. The template's Pages/_ViewStart.cshtml already selects this layout.
For everything the Toolkit script does, see What the Toolkit script does.
<htmx-config /> renders configuration and antiforgery metadata in the document head.
The Toolkit script uses that metadata when a form later sends a POST request.
For local hosting of HTMX, download the same file into wwwroot/js/htmx.min.js and change its script URL to
~/js/htmx.min.js. See the HTMX installation guide.
5. Add a model and partial view
Create Models/Product.cs:
namespace HtmxStarter.Models;
public sealed record Product(int Id, string Name, decimal Price);
Create Pages/_ProductDetails.cshtml:
@model Product
<article>
<h2>@Model.Name</h2>
<p>Product #@Model.Id · Price: @Model.Price</p>
<p>In stock.</p>
</article>
A partial contains only the HTML needed for an update. It has no @page directive and does not render
the layout. Razor encodes model values when it writes them into HTML.
6. Add the page and handler
Create Pages/Products.cshtml:
@page
@model ProductsModel
<h1>Products</h1>
<button hx-page="/Products"
hx-page-handler="Details"
hx-route-id="42"
hx-target="#product-details">
View product
</button>
<div id="product-details" aria-live="polite">
Select a product.
</div>
Create Pages/Products.cshtml.cs:
using HtmxStarter.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace HtmxStarter.Pages;
public sealed class ProductsModel : PageModel
{
public IActionResult OnGetDetails(int id)
{
if (id != 42)
return NotFound();
var product = new Product(42, "Notebook", 12.50m);
return Partial("_ProductDetails", product);
}
}
The data is created in the handler to keep this example self-contained. In an application, load it from your data service before returning the partial.
7. Run it
dotnet run --no-launch-profile -- --urls http://localhost:5080 --environment Development
Open http://localhost:5080/Products and select View product. The product details should replace “Select a product.” without a full page load.
Inspect the rendered button. Its routing attributes have become a URL similar to:
<button hx-get="/Products?id=42&handler=Details" hx-target="#product-details">
View product
</button>
Query parameter order may differ. The important parts are the page path, id=42, and handler=Details.
In the browser Network panel, the request should have HX-Request: true, return HTTP 200,
and contain only the <article> in its response. HTMX's default swap replaces the target's contents,
so the #product-details container remains available for another click.
The request should also have HX-Request-Type: partial. HTMX 2.x doesn't send this header itself;
the Toolkit script adds it, so seeing it confirms that the script is loaded.
If nothing happens, start with Clicking does not send a request.
Next steps
Continue with Forms, validation, and events. It adds a complete POST flow and refreshes a second component after a successful save. Then add loading states and request errors to handle slow or failed requests.
- Full pages and fragments explains when to return each kind of response.
- Tag Helpers covers URL generation, headers, values, and request options.
- Run the examples points to the larger demo and its source files.