Skip to content

Filters

A filter line begins with : followed by the filter name. The filter's body is the indented block beneath it. Each filter passes its body through a handler that returns rendered text.

1
2
:javascript
  alert('hi');

renders to:

1
2
3
<script>
  alert('hi');
</script>

Built-in filters

:plain

Emits the body verbatim with no sigil parsing and no HTML escaping. Interpolates #{ ... } expressions.

1
2
3
:plain
  Hello, #{$name}!
  <b>raw html stays raw</b>

:escaped

HTML-escapes the body (after interpolation), then emits.

1
2
:escaped
  <b>raw</b> & "stuff"

renders to:

1
&lt;b&gt;raw&lt;/b&gt; &amp; &quot;stuff&quot;

:javascript

Wraps the body in <script> ... </script>. Interpolates #{ ... }.

When the format is xhtml, or when the cdata config option is True, the body is additionally wrapped in //<![CDATA[ ... //]]> markers and the <script> tag emits type="text/javascript" (or whatever mime-type is set to). In HTML5 with mime-type set, the type attribute is emitted with no CDATA wrapping unless cdata: True.

:css

Wraps the body in <style> ... </style>. Interpolates #{ ... }.

:css mirrors :javascript for the cdata / mime-type config options: the CDATA markers use the /*<![CDATA[*/ ... /*]]>*/ form, and the default XHTML type attribute is text/css.

:cdata

Wraps the body in <![CDATA[ ... ]]>. Interpolates #{ ... }.

:preserve

Replaces newlines in the body with &#x000A; so the output stays on a single line. Useful for preserving whitespace inside a tag without inflating the parent indentation. Interpolates #{ ... }.

1
2
3
:preserve
  line1
  line2

renders to:

1
line1&#x000A;line2

:markdown

Renders the body as Markdown by delegating to a user-registered backend. Interpolates #{ ... } before the body is passed to the backend.

1
2
3
:markdown
  # Hello, #{$name}!
  body text

Template::HAML ships no Markdown engine of its own — Raku has several viable Markdown libraries and the right choice belongs to the host application. Register one with register-markdown-backend:

1
2
3
4
5
6
7
use Template::HAML;
use Template::HAML::Filters;
use Text::Markdown;  # or any other Markdown engine

register-markdown-backend :handler(-> Str $body --> Str {
  Text::Markdown.new(:text($body)).render;
});

The handler signature is (Str $body --> Str). The interpolated body is passed in; the returned string is the rendered HTML. The renderer prefixes each output line with the filter's own source indent, so a :markdown nested inside %div lines up correctly.

If :markdown appears in a template and no backend is registered, render fails with X::HAML::MarkdownBackendMissing. Related helpers:

Sub What it does
register-markdown-backend Install or replace the backend handler.
clear-markdown-backend Remove the current handler. Returns the previous defined-ness as a Bool.
markdown-backend-registered True if a backend is currently installed.
current-markdown-backend The current handler, or an undefined Callable.

Calling register-markdown-backend again replaces the previous handler — at most one Markdown backend is active per process.

:raku

Evaluates the body as Raku code with the current locals in scope and emits the return value as a string. The body is not interpolated — it is code.

1
2
:raku
  $x * 2

with :locals(%(x => 10)) renders to:

1
20

Custom filters

Register a filter via register-filter:

1
2
3
4
5
6
7
8
9
use Template::HAML;
use Template::HAML::Filters;

register-filter :name<upper>, :handler(-> Str $body, %locals --> Str {
  $body.uc;
});

say HAML.render(:src(":upper\n  hello\n"));
# HELLO

The handler signature is (Str $body, %locals --> Str). The $body is the filter's indented block dedented to column zero; the handler returns the rendered text. The renderer prefixes each output line with the filter's own source indent.

Interpolation summary

Filter Interpolates #{ ... }
:plain yes
:escaped yes
:javascript yes
:css yes
:cdata yes
:preserve yes
:markdown yes
:raku no (body is code)

Indentation

Body lines must be indented strictly more than the filter line. The minimum indent across all non-blank body lines is stripped, so relative indentation inside the body is preserved:

1
2
3
:plain
  outer
    inner

renders to:

1
2
outer
  inner

Internal blank lines inside the body are preserved. Trailing blank lines between the filter body and the next sibling are stripped.

Errors

An unknown filter name raises X::HAML::UnknownFilter at render time, with the offending name and source position.