# Template filters

Format text, prices, dates, and collections directly in your Go templates. Learn the calling conventions first, then use the reference below to find every registered filter.

## Available in every Go template {#overview}

HyperChi copies `filters.FuncMap()` when you create an application. Templates loaded with `app.LoadTemplates` can call these functions immediately. They are Go template functions, not a separate expression language. The external templ and Jet integrations do not inherit this function map.

```go
app := hyperchi.New()
helpers.Must(app.LoadTemplates("templates/*.tmpl"))

app.Get("/product", func(c *hyperchi.Context) error {
    return c.View("product.tmpl", hyperchi.H{
        "name": "  field notebook  ",
        "price": 24.5,
        "published": "2026-10-05T12:00:00Z",
    })
})
```

```html
<h1>{{ .name | trim | title }}</h1>
<p>{{ currency .price "$" }}</p>
<time>{{ .published | dateFormat "Jan 2, 2006" }}</time>
```

The page displays “Field Notebook”, “$24.50”, and “Oct 5, 2026”. The handler uses only the core `hyperchi` package. `c.View` hands the values to the template unchanged, so a `time.Time` works as well as an RFC 3339 string.

## Argument order matters {#arguments}

A Go pipeline places its result in the **last** argument. Unary functions work naturally in a chain. Most multi-argument HyperChi filters take the value **first**, so use a direct call or parentheses.

```html
{{ .name | trim | upper }}           <!-- unary chain -->
{{ truncate .name 20 }}             <!-- value first -->
{{ currency .price "€" }}
{{ default .nickname "Anonymous" }}
{{ join (sort .tags) ", " }}
{{ .published | dateFormat "2006-01-02" }} <!-- format first -->
```

> `{{ .name | truncate 20 }}` is not equivalent to `{{ truncate .name 20 }}`. It sends `20` as the first argument. Likewise, `default` takes the value before the fallback. `dateFormat` intentionally takes the format first and supports the pipeline shown above.

## Recipes with live output {#example}

These outputs are rendered by this page using the installed filters. Change the literals in your own templates to adapt them.

### Clean a label

```gotemplate
{{ "  field notebook  " | trim | title }}
```

Result: `Field Notebook`

### Shorten text

```gotemplate
{{ truncate "A notebook for every journey" 18 }}
```

Result: `A notebook for ...`

### Format a price

```gotemplate
{{ currency 1234.5 "$" }}
```

Result: `$1,234.50`

### Display a proportion

```gotemplate
{{ percent 0.125 1 }}
```

Result: `12.5%`

### Human-readable storage

```gotemplate
{{ bytes 1536 }}
```

Result: `1.50 KB`

### Format an ISO date

```gotemplate
{{ "2026-10-05T12:00:00Z" | dateFormat "Jan 2, 2006" }}
```

Result: `Oct 5, 2026`

### Use a fallback

```gotemplate
{{ default "" "Anonymous" }}
```

Result: `Anonymous`

### Sort a collection

```gotemplate
{{ range sort (list "htmx" "go" "html") }}{{ . }} {{ end }}
```

Result: `go html htmx`

### Read nested JSON

```gotemplate
{{ jsonGet (fromJSON `{"user":{"name":"Morgan"}}`) "user.name" }}
```

Result: `Morgan`

### Count items

```gotemplate
{{ len (list "Go" "HTML" "HTMX") }}
```

Result: `3`

## Work with collections {#collections}

`sortBy`, `filter`, and `pluck` accept a collection followed by a field name. Use map keys for maps such as `hyperchi.H`. For structs, use the exported Go field name. Sorting uses string representations, including in `sortBy`; sort numeric values in Go when numeric order matters. Keep database queries and business decisions in the handler.

```html
{{ $active := filter .products "active" true }}
{{ range take (sortBy $active "name") 3 }}
  <article>
    <h3>{{ .name }}</h3>
    <p>{{ currency .price }}</p>
  </article>
{{ else }}
  <p>No matching products.</p>
{{ end }}
```

`contains` is a substring test for strings and a membership test for slices and arrays (by the rules of `eq`, so `2` matches `2.0`). `join` expects `[]string`, while many collection transforms return `[]any`. Render those results with `range` instead of passing them to `join`.

## HTML, JSON, and htmx {#html}

Go’s `html/template` escapes ordinary values for their HTML context. Render visitor text directly; adding `escape` can double-escape it. `nl2br` escapes its text before inserting line breaks.

```html
<p>{{ .message }}</p>
<div>{{ nl2br .message }}</div>
<div data-settings="{{ toJSON .settings }}">Settings</div>
<button {{ hxGet "/demo/echo" }} {{ hxTarget "#result" }} {{ hxSwap "innerHTML" }}>
  Load a fragment
</button>
<div id="result" aria-live="polite"></div>
```

Load htmx separately to activate these attributes. Attribute helpers generate individual attributes: place them side by side, not in a pipeline. Keep attribute names, event expressions, and destinations application-controlled.

`safe`, `unescape`, `css`, and `js` mark content as trusted; they do not sanitize it. `stripTags` is a regular-expression transform that also returns trusted HTML, not a security boundary. Only use trusted-content helpers after an appropriate sanitization or application-owned-content boundary.

`htmxButton` and `htmxForm` return complete elements, not attributes. They take typed `filters.HTMXButtonOptions` and `filters.HTMXFormOptions`, not a `dict`. Pass the typed options in the handler data, or use literal elements with individual helpers, as above.

## Register a custom filter {#custom}

Register filters during startup, before loading templates and before serving traffic. Use a distinctive name so you do not accidentally replace a built-in function.

```go
app.RegisterFilter("excerpt", func(limit int, text string) string {
    if limit < 0 {
        limit = 0
    }
    runes := []rune(text)
    if len(runes) > limit {
        return string(runes[:limit]) + "…"
    }
    return text
})
helpers.Must(app.LoadTemplates("templates/*.tmpl"))
```

```html
{{ .description | excerpt 80 }}
```

This custom function deliberately puts text last for pipeline use. Register several functions with `app.RegisterFilters(template.FuncMap{...})`. To use the built-ins outside HyperChi, call `template.New("page").Funcs(filters.FuncMap()).Parse(source)`. Package-level `filters.Register` affects future snapshots; it does not update applications that already copied their function maps.

## Types and failure behavior {#errors}

Functions returning `(value, error)`, such as `fromJSON` and `dateAdd`, stop template execution on a non-nil error. Other helpers return fallback values: an unparseable `date` produces an empty string, while an unconvertible `currency` value is printed unchanged. Validate input in the handler rather than treating formatting as validation.

`default` treats false, numeric zero, and empty collections as empty, as well as empty strings and nil. Use `coalesce` when only nil should trigger a fallback. Validate sizes and indexes before passing visitor-controlled numbers to collection or truncation helpers.

## Complete registered reference {#reference}

Signatures below come from the running filter registry. Arguments appear in call order; `...` means variadic arguments and `interface {}` means `any`. Expand a category to inspect every function. Functions requiring typed options or callbacks are generally easier to use from Go with direct template execution.

### Strings (31 functions)

- `br2nl`: `func(string) string`
- `camelCase`: `func(string) string`
- `capitalize`: `func(string) string`
- `dedent`: `func(string) string`
- `hasPrefix`: `func(string, string) bool`
- `hasSuffix`: `func(string, string) bool`
- `indent`: `func(string, int, ...string) string`
- `join`: `func([]string, string) string`
- `kebabCase`: `func(string) string`
- `lower`: `func(string) string`
- `nl2br`: `func(string) template.HTML`
- `pascalCase`: `func(string) string`
- `pluralize`: `func(string, ...int) string`
- `quote`: `func(string) string`
- `repeat`: `func(string, int) string`
- `replace`: `func(string, string, string, int) string`
- `replaceAll`: `func(string, string, string) string`
- `singularize`: `func(string) string`
- `slug`: `func(string) string`
- `snakeCase`: `func(string) string`
- `split`: `func(string, string) []string`
- `stripTags`: `func(string) template.HTML`
- `title`: `func(string) string`
- `trim`: `func(string) string`
- `trimPrefix`: `func(string, string) string`
- `trimSuffix`: `func(string, string) string`
- `truncate`: `func(string, int, ...string) string`
- `truncateWords`: `func(string, int, ...string) string`
- `unquote`: `func(string) string`
- `upper`: `func(string) string`
- `wordWrap`: `func(string, int) string`

### HTML (27 functions)

- `addClass`: `func(string, string) string`
- `ariaAttrs`: `func(map[string]interface {}) (template.HTMLAttr, error)`
- `attr`: `func(string, string) (template.HTMLAttr, error)`
- `checkbox`: `func(string, interface {}, bool, ...template.HTMLAttr) template.HTML`
- `classNames`: `func(...interface {}) string`
- `css`: `func(string) template.CSS`
- `dataAttrs`: `func(map[string]interface {}) (template.HTMLAttr, error)`
- `escape`: `func(string) string`
- `formField`: `func(string, string, interface {}, ...template.HTMLAttr) template.HTML`
- `img`: `func(string, string, ...template.HTMLAttr) template.HTML`
- `inputAttrs`: `func(filters.InputOptions) (template.HTMLAttr, error)`
- `js`: `func(string) template.JS`
- `link`: `func(string, string, ...template.HTMLAttr) template.HTML`
- `mailto`: `func(string, ...string) template.HTML`
- `meta`: `func(string, string) template.HTML`
- `radio`: `func(string, interface {}, bool, ...template.HTMLAttr) template.HTML`
- `removeClass`: `func(string, string) string`
- `safe`: `func(string) template.HTML`
- `script`: `func(string, ...template.HTMLAttr) template.HTML`
- `select`: `func(string, []filters.SelectOption, ...template.HTMLAttr) template.HTML`
- `style`: `func(string, ...template.HTMLAttr) template.HTML`
- `tel`: `func(string, ...string) template.HTML`
- `textarea`: `func(string, interface {}, int, int, ...template.HTMLAttr) template.HTML`
- `toggleClass`: `func(string, string) string`
- `unescape`: `func(string) template.HTML`
- `url`: `func(string) string`
- `urlquery`: `func(string) string`

### JSON (12 functions)

- `compactJSON`: `func(string) (string, error)`
- `fromJSON`: `func(string) (interface {}, error)`
- `jsonArray`: `func(...interface {}) []interface {}`
- `jsonAttr`: `func(string, interface {}) (template.HTMLAttr, error)`
- `jsonEscape`: `func(string) string`
- `jsonGet`: `func(interface {}, string) interface {}`
- `jsonLD`: `func(interface {}) template.HTML`
- `jsonMerge`: `func(interface {}, interface {}) interface {}`
- `jsonObject`: `func(...interface {}) map[string]interface {}`
- `jsonSet`: `func(interface {}, string, interface {}) interface {}`
- `prettyJSON`: `func(interface {}) (string, error)`
- `toJSON`: `func(interface {}) (string, error)`

### Dates and times (26 functions)

- `date`: `func(interface {}, ...string) string`
- `dateAdd`: `func(interface {}, int, string) (time.Time, error)`
- `dateCompare`: `func(interface {}, interface {}) (int, error)`
- `dateDiff`: `func(interface {}, interface {}, string) (int64, error)`
- `dateFormat`: `func(string, interface {}) string`
- `dateSub`: `func(interface {}, int, string) (time.Time, error)`
- `day`: `func(interface {}) int`
- `durationSeconds`: `func(time.Duration) float64`
- `fromUnix`: `func(int64) time.Time`
- `hour`: `func(interface {}) int`
- `humanDuration`: `func(interface {}) string`
- `iso8601`: `func(interface {}) string`
- `minute`: `func(interface {}) int`
- `month`: `func(interface {}) string`
- `now`: `func() time.Time`
- `rfc3339`: `func(interface {}) string`
- `second`: `func(interface {}) int`
- `timeAgo`: `func(interface {}) string`
- `timeFrom`: `func(interface {}) string`
- `timezone`: `func(interface {}, string) (time.Time, error)`
- `today`: `func() time.Time`
- `tomorrow`: `func() time.Time`
- `unix`: `func(interface {}) int64`
- `weekday`: `func(interface {}) string`
- `year`: `func(interface {}) int`
- `yesterday`: `func() time.Time`

### Numbers (25 functions)

- `abs`: `func(interface {}) float64`
- `add`: `func(interface {}, interface {}) float64`
- `avg`: `func(...interface {}) float64`
- `bytes`: `func(interface {}) string`
- `ceil`: `func(interface {}) float64`
- `commas`: `func(string) string`
- `currency`: `func(interface {}, ...string) string`
- `decimal`: `func(interface {}, int) string`
- `div`: `func(interface {}, interface {}) float64`
- `floor`: `func(interface {}) float64`
- `humanize`: `func(interface {}) string`
- `max`: `func(...interface {}) float64`
- `min`: `func(...interface {}) float64`
- `mod`: `func(interface {}, interface {}) float64`
- `mul`: `func(interface {}, interface {}) float64`
- `number`: `func(interface {}, ...int) string`
- `ordinal`: `func(interface {}) string`
- `padNumber`: `func(interface {}, int, ...string) string`
- `percent`: `func(interface {}, ...int) string`
- `pow`: `func(interface {}, interface {}) float64`
- `roman`: `func(interface {}) string`
- `round`: `func(interface {}, ...int) float64`
- `sqrt`: `func(interface {}) float64`
- `sub`: `func(interface {}, interface {}) float64`
- `sum`: `func(...interface {}) float64`

### Collections (28 functions)

- `chunk`: `func(interface {}, int) [][]interface {}`
- `contains`: `func(interface {}, interface {}) bool`
- `filter`: `func(interface {}, string, interface {}) []interface {}`
- `find`: `func(interface {}, string, interface {}) interface {}`
- `findIndex`: `func(interface {}, string, interface {}) int`
- `first`: `func(interface {}) interface {}`
- `flatten`: `func(interface {}) []interface {}`
- `group`: `func(interface {}, string) map[string][]interface {}`
- `index`: `func(interface {}, int) interface {}`
- `keys`: `func(interface {}) []string`
- `last`: `func(interface {}) interface {}`
- `len`: `func(interface {}) int`
- `map`: `func(interface {}, string) []interface {}`
- `merge`: `func(...interface {}) map[string]interface {}`
- `pairs`: `func(interface {}) [][]interface {}`
- `pluck`: `func(interface {}, string) []interface {}`
- `random`: `func(interface {}) interface {}`
- `reduce`: `func(interface {}, func(interface {}, interface {}) interface {}, interface {}) interface {}`
- `reverse`: `func(interface {}) []interface {}`
- `shuffle`: `func(interface {}) []interface {}`
- `skip`: `func(interface {}, int) []interface {}`
- `slice`: `func(interface {}, int, ...int) []interface {}`
- `sort`: `func(interface {}) []interface {}`
- `sortBy`: `func(interface {}, string) []interface {}`
- `take`: `func(interface {}, int) []interface {}`
- `unique`: `func(interface {}) []interface {}`
- `values`: `func(interface {}) []interface {}`
- `zip`: `func(...interface {}) [][]interface {}`

### HTMX (39 functions)

- `htmxAttrs`: `func(filters.HTMXAttrs) (template.HTMLAttr, error)`
- `htmxButton`: `func(filters.HTMXButtonOptions) (template.HTML, error)`
- `htmxForm`: `func(filters.HTMXFormOptions) (template.HTML, error)`
- `htmxLink`: `func(string, string, ...map[string]string) (template.HTML, error)`
- `htmxModal`: `func(string, template.HTML) template.HTML`
- `htmxSpinner`: `func(string) template.HTML`
- `htmxToast`: `func(string, string) template.HTML`
- `hxAction`: `func(string) template.HTMLAttr`
- `hxBoost`: `func(bool) template.HTMLAttr`
- `hxConfig`: `func(map[string]interface {}) template.HTMLAttr`
- `hxConfirm`: `func(string) template.HTMLAttr`
- `hxDelete`: `func(string) template.HTMLAttr`
- `hxDisabledElt`: `func(string) template.HTMLAttr`
- `hxEncoding`: `func(string) template.HTMLAttr`
- `hxGet`: `func(string) template.HTMLAttr`
- `hxHeaders`: `func(map[string]string) template.HTMLAttr`
- `hxIgnore`: `func() template.HTMLAttr`
- `hxInclude`: `func(string) template.HTMLAttr`
- `hxIndicator`: `func(string) template.HTMLAttr`
- `hxMethod`: `func(string) template.HTMLAttr`
- `hxOn`: `func(string, string) (template.HTMLAttr, error)`
- `hxOptimistic`: `func(string) template.HTMLAttr`
- `hxPatch`: `func(string) template.HTMLAttr`
- `hxPost`: `func(string) template.HTMLAttr`
- `hxPreload`: `func(string) template.HTMLAttr`
- `hxPreserve`: `func(interface {}) template.HTMLAttr`
- `hxPushURL`: `func(string) template.HTMLAttr`
- `hxPut`: `func(string) template.HTMLAttr`
- `hxReplaceURL`: `func(interface {}) template.HTMLAttr`
- `hxSelect`: `func(string) template.HTMLAttr`
- `hxSelectOOB`: `func(string) template.HTMLAttr`
- `hxStatus`: `func(string, string) (template.HTMLAttr, error)`
- `hxSwap`: `func(string) template.HTMLAttr`
- `hxSwapOOB`: `func(string) template.HTMLAttr`
- `hxSync`: `func(string) template.HTMLAttr`
- `hxTarget`: `func(string) template.HTMLAttr`
- `hxTrigger`: `func(string) template.HTMLAttr`
- `hxValidate`: `func(bool) template.HTMLAttr`
- `hxVals`: `func(map[string]interface {}) template.HTMLAttr`

### Utilities (37 functions)

- `and`: `func(...interface {}) bool`
- `base64Decode`: `func(string) (string, error)`
- `base64Encode`: `func(string) string`
- `coalesce`: `func(...interface {}) interface {}`
- `default`: `func(interface {}, interface {}) interface {}`
- `dict`: `func(...interface {}) map[string]interface {}`
- `empty`: `func(interface {}) bool`
- `env`: `func(string, ...string) string`
- `eq`: `func(interface {}, ...interface {}) bool`
- `fallback`: `func(...interface {}) interface {}`
- `ge`: `func(interface {}, interface {}) bool`
- `gt`: `func(interface {}, interface {}) bool`
- `isNil`: `func(interface {}) bool`
- `kindOf`: `func(interface {}) string`
- `le`: `func(interface {}, interface {}) bool`
- `list`: `func(...interface {}) []interface {}`
- `lt`: `func(interface {}, interface {}) bool`
- `ne`: `func(interface {}, interface {}) bool`
- `not`: `func(interface {}) bool`
- `notEmpty`: `func(interface {}) bool`
- `notNil`: `func(interface {}) bool`
- `or`: `func(...interface {}) bool`
- `pathBase`: `func(string) string`
- `pathDir`: `func(string) string`
- `pathExt`: `func(string) string`
- `pathJoin`: `func(...string) string`
- `print`: `func(...interface {}) string`
- `printf`: `func(string, ...interface {}) string`
- `println`: `func(...interface {}) string`
- `regexMatch`: `func(string, string) (bool, error)`
- `regexReplace`: `func(string, string, string) (string, error)`
- `regexSplit`: `func(string, string) ([]string, error)`
- `ternary`: `func(bool, interface {}, interface {}) interface {}`
- `typeOf`: `func(interface {}) string`
- `urlDecode`: `func(string) (string, error)`
- `urlEncode`: `func(string) string`
- `uuid`: `func() (string, error)`

## Keep exploring {#next}

Read [Templates](/docs/templates) for composition, [Passing data](/docs/data) for view models, or the [filter implementations and tests](https://github.com/regiellis/hyperchi/tree/main/hyperchi/filters) for detailed edge behavior. Formatting helpers do not automatically transfer to [templ](/docs/templ) or [Jet](/docs/jet); those renderers own their data and helper APIs.
