Skip to main content
A template renders against a JSON context. Page content comes from reading values out of that context and interpolating them.

Interpolation

The output is minified, which is why the closing </p> is missing. HTML makes it optional there and Slurp drops it. Every output sample on these pages is minified. Use the ${ } form. A bare { } means exactly the same thing and is accepted, but it reads ambiguously next to a block tag such as {if}, so these guides use ${ } throughout. Everything interpolated is escaped for the context it lands in. A value containing <script> renders as text in a paragraph, and as an escaped attribute value in an attribute. Escaping is always on. See Escaping for the details and for the one tag that bypasses it.

Property access

Dots walk into objects, brackets index arrays and string keys.
A dynamic index works, including with an {each} loop index. After a . only an identifier is accepted, so items.0 is a parse error and items[0] is the only spelling. Navigation is null-safe at every depth, and there is no ?. operator.

Missing values render nothing

A property that is not there resolves to null and renders the empty string. So does a whole missing chain. Nothing is reported, at any stage.
A typo in a property name does not raise. It renders a page that looks almost right with one thing quietly blank, and this is the root of most time lost to Slurp. If a value is not appearing, check the spelling of the path first.
Slurp renders templates the host did not write, where aborting a page because one field is absent is the wrong trade. Build with --verbose, and read Common mistakes.

Operators

Arithmetic is + - * / %, comparison is == != < > <= >=, logic is && || !, and there is a ternary and a backtick template literal. && and || return the operand value rather than a boolean, so user.nickname || user.name is the usual way to write a fallback for a value that might be blank. For a value that might be missing entirely, prefer | default(...). A few behaviours differ from JavaScript: Any arithmetic result that would be NaN or infinite becomes 0.

Arithmetic coerces, comparison does not

This asymmetry produces a wrong page rather than an error.
Arithmetic coerces freely. A non-numeric string, null, an array and an object all become 0, and "10" - 1 is 9. Comparison does not coerce at all. Only number against number and string against string actually compare. Every other pairing is treated as equal, so > and < are false, and >= and <= are false too because the two values are not equal either. A string that looks like a number loses every comparison it is in. Server data arrives as strings often. Postgres and most other databases serialise a decimal to JSON as a string, so a price or a stock count is frequently "10" rather than 10. Coerce it:
| int and | float are the two coercions, and both are all-or-nothing: "12abc" becomes 0, not 12.

Truthiness

null, false, 0, the empty string, the empty array and the empty object are falsy. Everything else is truthy. Two of those differ from JavaScript:
  • An empty array is falsy, so {if cart.items} is a valid emptiness check.
  • The strings "0" and "false" are truthy, because they are non-empty strings. A boolean that arrived from a form or a query string as text needs comparing explicitly.

What expressions cannot do

Slurp expressions are not a programming language. A template cannot run code at render time. Several habits carried over from other engines silently produce nothing. There are no callable functions. A call parses and always evaluates to null, with no diagnostic:
items.length behaves the same way: there are no array properties or methods. The only call-shaped construct in the language is a filter. These operators do not exist: ===, !==, ??, ?., in, **, typeof, instanceof, every bitwise operator, every assignment operator, and arrow functions. Unlike a bad property name, writing one of these is a parse error rather than a silent failure. There are no array or object literals. Both [1, 2] and {a: 1} are parse errors. Pass collections in through the render context.

Interpolating into attributes

A quoted element attribute interpolates, and an expression attribute in braces does too:
An expression attribute that evaluates to empty is omitted entirely, rather than emitted blank:
A quoted component prop does not interpolate. <Card title="Hi ${name}" /> passes those characters as literal text. That asymmetry is the most common mistake in the language and it is covered in Components.

A nested double quote ends the attribute

The lexer closes an attribute value at its first matching quote, before interpolation is considered at all. So a double-quoted string key inside a double-quoted attribute truncates the attribute and turns the remainder into stray attributes. Nothing is reported by the compiler. Two spellings work:

How data reaches a template

At build time it comes from a JSON file, and every top-level key becomes a global:
data.json
Without --globals the context is empty, and because missing values render nothing, the build still succeeds and every page comes out with the data missing. If a whole page renders blank, check that flag first. When Slurp is embedded in a server the picture changes: the host supplies the context per request, and --globals covers only the values a theme must resolve at compile time. See Embedding with Rust. Inside the template there is no difference between the two: a page cannot tell where its context came from. A theme can therefore be developed against a fixture file and shipped against live data.

Next

Control flow

Conditionals, loops and the iteration budget.

Filters

Formatting values on the way out.

Escaping

Per-context escaping, and the unescaped forms.

Expressions

The full grammar, precedence table and operator semantics.