Built‑in Extensions
Extensions are the verbs of Heddle: every @name(...) directive invokes an extension. This page documents the extensions bundled with the Heddle assembly. Each entry lists the name ([ExtensionName]), the expected input type ([DataType], when declared), whether the output is HTML‑encoded, and what the optional parameter / subtemplate mean.
All names and attributes below were read directly from src/Heddle/Extensions. To write your own, see Writing Custom Extensions.
How to read these entries
- Input — the model value the extension expects (its
[DataType]). If the actual model doesn't match, most extensions render empty rather than throw. - Body / parameter — many extensions interpret their
{{ … }}subtemplate as a format string or item template rather than literal output (e.g.@date(d){{ yyyy-MM-dd }}). - Body context — for the conditionals and formatters (
if,ifnot,date,time,int,money,guid,string) andfor, the parameter is a value to test/format and the body renders in the caller's context (one step back), so@if(flag){{ @(Title) }}still sees the surrounding model.listand definitions instead render the body against the parameter. See Language Reference → stepping back. - HTML‑encoded — extensions marked
[EncodeOutput](and deriving fromAbstractHtmlExtension) HTML‑encode their result by default. See HTML encoding at the bottom.
Conditionals
if
IfExtension.cs · input: bool (or any object)
Renders its subtemplate when the condition is truthy. A null model renders nothing; a bool model renders the body only when true; a non‑bool, non‑null model is treated as present (renders the body).
@if(IsFeatured){{ <span class="badge">Featured</span> }}
@if(@model.Comments.Count > 0){{ <h3>Comments</h3> }}Because a non‑null, non‑bool value counts as present, @if(member) is the idiomatic "if present" check for a nullable reference: @if(Summary){{ <p>@(Summary)</p> }} renders only when Summary is set. The body runs in the caller's context (see stepping back), so @(Summary) inside it still refers to the surrounding model.
ifnot
IfNotExtension.cs · input: bool (or null)
The inverse of if. Renders its body when the model is null or false. A non‑bool, non‑null model renders nothing — so @ifnot(member) is the idiomatic "if absent" check.
@ifnot(IsFeatured){{ <span class="muted">Regular post</span> }}
@ifnot(Summary){{ <p class="muted">No summary yet.</p> }}There is no
else/elif. Combine@if/@ifnot(often nested) for the branches you need — see Patterns → conditionals.
Iteration
list
ListExtension.cs · input: IEnumerable
Renders its subtemplate once per element; inside the body the current model is the element, and an int index is available as the chained value. Works with both dynamic sequences and strongly‑typed IEnumerable<T> (the element type is inferred for typed member access). When the source implements ICollection<T>, the count is used to pre‑size the output buffer.
@list(Articles){{ @article_card() }} @* each element is an Article *@
@list(Tags){{ <span class="tag">@()</span> }} @* @() is the current tag string *@
@list(@model.Articles.Where(a => a.IsFeatured)){{ @article_card() }} @* filtered *@for
ForIndexExtension.cs · input: ForModel
A counted loop. The model is a ForModel { Start?, Last, Step? } (Heddle.Models); it iterates i = Start (default 0) while i < Last, incrementing by Step (default 1). The loop index is exposed to the body as the chained value (referenceable as chained in embedded C#).
@for(@new ForModel() { Last = model.Articles.Count(), Step = 3 })
{{
<div class="row">
@list(@model.Articles.Skip(chained).Take(3)){{ @article_card() }}
</div>
}}(chained is the loop index; here it pages the articles three at a time.)
Formatting
These all derive from AbstractHtmlExtension and are [EncodeOutput] (except guid). Their optional body is the format string.
date
DateExtension.cs · input: DateTime · HTML‑encoded
Formats a DateTime using the body as a .NET date format string (default "d"), with CultureInfo.InvariantCulture.
@date(PublishedOn){{ MMMM d, yyyy }} @* e.g. June 28, 2026 *@
@date(PublishedOn) @* default short date *@time
TimeExtension.cs · input: DateTime · HTML‑encoded
Like date but defaults to the "t" (short time) format.
@time(PublishedOn){{ HH:mm }}int
IntegerExtension.cs · input: int / long · HTML‑encoded
Formats an integer; the body is an optional numeric format string. Other numeric types are converted to long when possible (invariant culture); non‑convertible values render empty.
@int(Year) @* 2026 *@
@int(@model.Comments.Count){{ 0:N0 }} @* 1,200 *@money
MoneyExtension.cs · input: decimal · HTML‑encoded
Formats a decimal as currency ("c"). The body is an optional culture name; with no body the current culture is used. Cultures are cached.
@money(@9.99m){{ en-US }} @* $9.99 — body is the culture name *@
@money(@1234.5m) @* current culture *@guid
GuidExtension.cs · input: Guid · not encoded
Formats a Guid; the body is an optional .NET GUID format specifier (N, D, B, P, X).
@guid(@System.Guid.NewGuid()){{N}} @* 32 digits, no dashes *@
@guid(@System.Guid.NewGuid()) @* default format *@string
StringExtension.cs · input: string (or any) · HTML‑encoded
Converts the model to a string. The optional body is a default/fallback rendered when the model is null. Non‑string models are converted with Convert.ChangeType (invariant culture), falling back to ToString().
@string(Title)
@string(Author.Name){{ Anonymous }} @* fallback when Author.Name is null *@Output and context
Empty / unnamed
EmptyExtension.cs · name: ""
The extension with the empty name backs the unnamed call form @(...). If it has a body it renders the body; otherwise it stringifies the current model (or empty when null). This is the workhorse behind @(Title), @(), etc.
The unnamed
@(...)form is not HTML‑encoded. For encoded text output usestring(orhtmlbelow).
html
EmptyHtmlExtension.cs · name: html · HTML‑encoded
Same behavior as the empty extension, but HTML‑encodes its output ([EncodeOutput]). Use it when you want the "just stringify the value" behavior with encoding.
@html(UserSuppliedText)out
OutExtension.cs · name: out
Emits the chained value — i.e. the data handed to the current definition/call. With no body it renders the chained value directly; with a body it renders that body against the chained data. This is how a definition surfaces the caller's inline content.
<article_card>
{{ <article><h2>@(Title)</h2>@out()</article> }} @* @out() drops in the caller's body *@@out() is the mechanism behind layouts: a layout definition wraps the page chrome around a central @out(), and each page supplies the content. See Language Reference → composition.
swap
SwapExtension.cs · name: swap
Swaps the model and chained values for the duration of its body — useful when an extension chain produced a value you now want to treat as the model.
param
ParamExtension.cs · name: param
Passes the model through unchanged as the chain value (a no‑op renderer that yields the current model as the chained input of the call to its left).
Declarations (no output)
These configure compilation; they emit nothing.
model
ModelExtension.cs · name: model
Declares the document's model type from within the template (instead of in C# code). The body is a type name resolved against the imported namespaces; dynamic is allowed.
@model(){{Blog}}
@model(){{dynamic}}using
UsingExtension.cs · name: using
Imports a C# namespace so that type names (in @model(), :: Type) and embedded C# can resolve unqualified identifiers.
@using(){{System.Linq}}
@using(){{MyBlog.Models}}import
ImportExtension.cs · name: import
Compile‑time include: reads another template file (relative to RootPath) and parses its definitions into the current parse context. This is the extension behind the @<<{{ path }} sugar. Emits no output of its own.
Embedding whole templates
partial
PartialExtension.cs · name: partial
Compiles a separate template by name (its body is the template name) and renders that template's output inline at run time, passing the current model and chained data. Unlike import, a partial produces output.
@partial(){{ sidebar }} @* compiles & renders the "sidebar" template by name *@The body (sidebar) names a separate template file the engine compiles and renders inline, passing the current model.
Quick reference
| Name | Source | Input type | Encoded | Body means |
|---|---|---|---|---|
if | IfExtension | bool/any | – | content shown when true |
ifnot | IfNotExtension | bool/null | – | content shown when false/null |
list | ListExtension | IEnumerable | – | per‑element template |
for | ForIndexExtension | ForModel | – | per‑iteration template |
date | DateExtension | DateTime | ✔ | date format string (def. d) |
time | TimeExtension | DateTime | ✔ | time format string (def. t) |
int | IntegerExtension | int/long | ✔ | numeric format string |
money | MoneyExtension | decimal | ✔ | culture name |
guid | GuidExtension | Guid | – | GUID format specifier |
string | StringExtension | string/any | ✔ | fallback when null |
| (empty) | EmptyExtension | any | – | optional body, else stringify model |
html | EmptyHtmlExtension | any | ✔ | optional body, else stringify model |
out | OutExtension | chained | – | template over chained data |
swap | SwapExtension | – | – | body with model/chained swapped |
param | ParamExtension | any | – | pass model through chain |
model | ModelExtension | – | – | model type name |
using | UsingExtension | – | – | namespace to import |
import | ImportExtension | – | – | file to include (definitions) |
partial | PartialExtension | model | – | template name to render |
HTML encoding
Encoding is decided per extension, not globally:
- Extensions marked
[EncodeOutput](and deriving fromAbstractHtmlExtension) HTML‑encode their output withWebUtility.HtmlEncode. These aredate,time,int,money,string, andhtml. - The unnamed
@(...)output and non‑HTML extensions (guid,out,list,if, …) emit their text as‑is. - The
[NotEncode]attribute, applied to a model property, marks that property's value as pre‑trusted so it is not encoded even when flowing through an encoding extension. See src/Heddle/Attributes/NotEncodeAttribute.cs.
To emit user‑controlled text safely, prefer @string(...) or @html(...) over the bare @(...) form.