April 4, 2026
Introducing Pennington
By Phil Scott
Pennington is a content engine for .NET. Point it at a folder of markdown and it builds a site — with navigation, search, feeds, and a static build included.
If you've put together a documentation site in .NET before, you've probably
either reached for a JavaScript static-site generator, which means a second
toolchain, or hand-rolled Razor pages, which means a lot of layout plumbing.
A folder of markdown, a working site
Pennington ships two templates. DocSite is for documentation: sidebar navigation, an on-page table of contents, and cross-references between pages. BlogSite is for posts: a home page, an archive, browse-by-tag pages, and an RSS feed. Wiring either into an ASP.NET app takes a few lines:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDocSite();
var app = builder.Build();
app.UseDocSite();
await app.RunDocSiteAsync(args);
RunDocSiteAsync does two jobs. Run the project normally and it's a live dev
server with hot reload. Pass build arguments and the same project emits static
HTML you can host anywhere — see how dev mode and build mode share a code
path.
If you'd rather not write those lines, dotnet new pennington-docs scaffolds
the project for you.
Code samples that stay in sync
Code samples in docs are usually copy-pasted snippets: correct the day they're written, slowly wrong after that. Pennington can pull samples from your source files instead. You reference a member by name path, and the current source renders at build time:
```csharp:symbol
src/Pennington/Pipeline/ContentPipeline.cs > ContentPipeline
```
When that type changes, the sample changes with it on the next build — there's no copy to keep up to date. Pennington can also embed whole files, diff two versions of a symbol, and highlight or focus lines; the code-block argument reference has the full set.
It's the feature several other posts on this blog come back to. To build a site of your own, start with creating your first Pennington site.