Skip to content

Heddle Documentation

Heddle is a text template engine for .NET, written in C#. It compiles templates written in its small, purpose‑built language into reusable, strongly‑typed renderers. Heddle can embed real C# expressions, define and inherit reusable named blocks, compose output through extension chains, and render with full HTML‑encoding control.

The engine is published as a set of NuGet packages:

PackageProjectPurpose
Heddlesrc/HeddleCore engine: parser host, compiler, runtime, built‑in extensions.
Heddle.Languagesrc/Heddle.LanguageANTLR grammar + generated lexer/parser, plus editor (Ace) assets.
Heddle.Generatorsrc/Heddle.GeneratorBuild‑time source generator that pre‑compiles .heddle files into your assembly. Reference with PrivateAssets="all" (an analyzer package).
Heddle.LanguageServicessrc/Heddle.LanguageServicesEditor language‑service facade (completion, diagnostics, hover, go‑to‑definition) you can host yourself.
Heddle.LanguageServersrc/Heddle.LanguageServerLSP server for editors, shipped as a dotnet tool (heddle-lsp).
Heddle.Toolsrc/Heddle.ToolThe heddle CLI — a dotnet tool for rendering templates and build‑time code generation (the T4 successor).

Current release line: 2.0.0. The published version is set from the latest release tag (vX.Y.Z) at publish time — see nuget.org — so the version in the source tree is just a placeholder.


Why Heddle?

  • Compiled and strongly typed. A template is compiled into an execution‑ready document — extension calls wired to compiled accessors (member paths to expression‑tree delegates, embedded C# to Roslyn delegates). With a typed model, member access and embedded C# are checked at compile time rather than discovered at render time; declaring @model(){{dynamic}} instead opts into render‑time member binding.
  • Fast. In this repository's benchmark run of 2026‑07‑11, Heddle rendered faster than ASP.NET Core Razor and allocated less memory (Razor's page is larger and not parity‑checked — the like‑for‑like comparison is against the four parity‑checked Liquid/Handlebars engines, which Heddle leads on render time and where it allocates the least or tied‑least memory — Handlebars.Net is within ~0.3 KB). See Architecture → Performance and the benchmark project.
  • Composable without coupling. Reusable templates are declarative extension points, so a page can be split into independent pieces recombined by a layout — at no runtime cost — and any page can serve as a base for another. See Language Reference → inheritance.
  • Extensible by design. The language has essentially one primitive — the extension call — so new directives are added as classes, not grammar. See Writing Custom Extensions.

Pick your path

I want to write templates (template authors)

Start with Getting Started, then read the Language Reference for every construct and its behavioral nuances, and Built‑in Extensions for the bundled helpers (list, if, date, money, …). For the sandbox‑safe expression tier — operators, literals, registered functions, ExpressionMode — see Native Expressions. For task‑oriented idioms, see Patterns & Recipes, and for editor tooling see Editor Support. Arriving from another engine? Start with Coming from Razor or Coming from Liquid (the latter also covers Jinja/Twig).

I want to use the engine from C# (integrators)

Read Getting Started and the C# API Reference (HeddleTemplate, TemplateOptions, CompileContext, compile results, file watching). To compile .heddle files into your assembly at build time, see Build‑Time Pre‑compilation. For complete, runnable integration examples — SSR, definition libraries, dynamic models, sandboxing, safe output, custom extensions, component libraries, codegen, precompilation, streaming — see the integration sample gallery. Replacing a Razor view layer? The Coming from Razor guide maps the concepts across.

I want to extend the engine (advanced)

Read Writing Custom Extensions to add your own @yourhelper(...) directives and register them via assembly attributes.

I want to understand or modify the engine (contributors)

Read the Architecture (lex → parse → compile → render pipeline, Roslyn code generation, lexer modes) and Building & Testing.


Documentation map

DocumentWhat it covers
Getting StartedInstall/build prerequisites and your first inline and file‑based templates.
Coming from LiquidMigration guide for Liquid/Jinja/Twig authors: {{ }} as a body, filter chains, include/render, loops, tags, whitespace control.
Coming from RazorMigration guide for ASP.NET Core Razor authors: relative context, layouts, @:, the three C# tiers, tag helpers → extensions.
Language ReferenceEvery Heddle construct: output, expressions, embedded C#, definitions, inheritance, subtemplates, chaining, imports, comments, raw blocks, whitespace, and the lexer‑mode model.
Native ExpressionsThe sandbox‑safe expression tier: operators, literals, registered functions, ExpressionMode.
Built‑in ExtensionsReference for every bundled extension, its expected input type, and HTML‑encoding behavior.
Patterns & RecipesTask‑oriented idioms: with‑blocks, presence checks, first/last, local helper definitions, root context, JSON injection, and more.
C# API ReferenceIHeddleTemplate/HeddleTemplate, TemplateOptions, CompileContext, HeddleCompileResult, registration, and error handling.
Build‑Time Pre‑compilationCompiling .heddle files into the assembly: generator setup, typed entry points, registry, mismatch policy.
Writing Custom ExtensionsThe extension contract, Scope, attributes, and registration.
ArchitectureInternal pipeline, ANTLR grammar, compilation, and editor tooling.
Syntax HighlightingThe portable TextMate grammar, its token→scope mapping, and how editors/sites consume it.
Editor SupportLSP server, VS Code extension, Neovim setup, configuration.
Building & TestingSDK, build scripts, target frameworks, tests, packaging, and CI.

A 30‑second taste

heddle
@model(){{dynamic}}
<p>Hi @(Name) — you have @int(Count) new comments.</p>
csharp
HeddleTemplate.Configure(typeof(Program).Assembly);

var source = "@model(){{dynamic}}\n<p>Hi @(Name) — you have @int(Count) new comments.</p>";
using var template = new HeddleTemplate(source, new CompileContext(new TemplateOptions()));

string html = template.Generate(new Greeting { Name = "Ada", Count = 3 });
// => <p>Hi Ada — you have 3 new comments.</p>

// A named type, not an anonymous one: the runtime binder behind @(Name)/@int(Count) can't
// see another assembly's anonymous types.
public class Greeting { public string Name { get; set; } public int Count { get; set; } }