Core reference · Values & operators
Expressions.
Nift uses one expression model across $[...], conditions and advanced templating. Declarations and assignment use this expression language too, but are documented separately so this page can stay focused on values, operators, precedence and evaluation.
$[...] evaluates an expression and renders its scalar result. @if(...) evaluates the same kind of expression and applies Nift truthiness to decide whether its branch renders. You do not need to learn a separate condition grammar.
The same expression in $[...] and @if(...)
$[price * quantity]
@if(price * quantity >= 100){
<p>Large order</p>
} The first expression renders a derived numeric value. The second uses the same arithmetic and comparison rules to produce a boolean condition.
$[post.published && !post.draft]
@if(post.published && !post.draft){
...
} In $[...], that logical expression renders true or false. In @if(...), the resulting value controls the branch.
Values and lookups
A plain value lookup is the simplest expression:
$[title]
$[post.author.name]
$[posts[2].title]
$[loop.index]
$[paginate.current] Expression values can come from built-in page/build metadata, JSON bindings, project contracts, loop bindings, pagination metadata and v4.1 lexical bindings.
Arithmetic
Numeric expressions support binary +, -, *, / and %, plus unary +/- and parentheses.
$[2 + 3 * 4] // 14
$[(2 + 3) * 4] // 20
$[-2 + 5] // 3
$[10 / 4] // 2.5
$[10 % 3] // 1
$[loop.index + 1]
$[price * quantity] + concatenates when either operand is a string (including mixed scalars such as "n=" + 42), concatenates arrays when both operands are arrays ([1, 2] + [3] → [1, 2, 3]), and performs numeric addition otherwise. Array concatenation builds a new array and never mutates its inputs. Nift does not coerce booleans, null, arrays or objects into numbers. Use ordinary template composition for text, for example Hello $[name].
Numeric basics & conversions
Numbers expose abs(), floor(), ceil() and round() (round half away from zero) as non-mutating methods. Conversions are explicit and directional: "42".to_int() and "3.14".to_double() parse strings into numbers. value.to_string() renders any ordinary scalar to text: numbers (42, 4.5), strings (themselves), booleans ("true"/"false") and null ("null"). Arrays and objects are not silently stringified — use .stringify() for a JSON/data representation. min/max are collection operations ([a, b].min()).
$[(-3).abs()] // 3
$[(3.7).floor()] // 3
$[(3.7).ceil()] // 4
$[(3.5).round()] // 4
$[(-2.5).round()] // -3
$["42".to_int()]
$["3.14".to_double()]
$[(3.5).to_string()]
$[true.to_string()]
$[null.to_string()]
$[[1, 2] + [3, 4]]
$[[a, b].min()]
$[[a, b].max()] to_int()/to_double() operate on strings and reject malformed or out-of-range input with an error; they are not numeric coercion of other value kinds.
Division or modulo by zero is a build error. Modulo requires integer-valued operands; fractional modulo is rejected. Non-finite arithmetic results are rejected rather than emitted into generated output.
Comparisons
Expressions support ==, !=, <, <=, > and >=.
$[score >= 50]
@if(item.priority >= site.minimum_priority){ ... }
@if(item.type == "article"){ ... } Numbers order numerically and strings lexicographically. Integers are signed 64-bit values and floating-point values use double; integer and double values can be compared numerically. Nift does not coerce strings or booleans into numbers. Data values use structural equality where supported, while identity-bearing values can be compared explicitly with same(a, b).
Increment, decrement and compound assignment
$[count := 1]
$[count++]
$[++count]
$[count += 5]
$[count -= 2]
$[count *= 3]
$[count /= 2]
$[count %= 4] Prefix increment/decrement returns the updated value; postfix returns the previous value. The target must be a mutable numeric lvalue. Compound assignment preserves the binding's stable type; += also supports string concatenation for string bindings.
Logic and short-circuiting
Use !, && and || to compose conditions. Logical operators short-circuit: an operand that cannot affect the result is not resolved.
@if(post.published && !post.draft){ ... }
@if((post.featured || post.pinned) && post.published){ ... }
$[post.published && !post.draft] This matters for correctness as well as convenience: a skipped logical operand is not needlessly resolved.
Precedence and parentheses
Nift follows conventional precedence. From tightest to loosest:
| Level | Operators |
|---|---|
| Postfix | x++, x-- |
| Unary | !, unary +, unary -, ++x, --x |
| Multiplicative | *, /, % |
| Additive | +, - |
| Comparison | ==, !=, <, <=, >, >= |
| Logical AND | && |
| Logical OR | || |
| Conditional | ?: |
| Assignment | =, +=, -=, *=, /=, %= |
| Declaration | := |
$[2 + 3 * 4] // 14
$[(2 + 3) * 4] // 20
@if((a || b) && c){ ... } Use parentheses whenever they make the intended grouping clearer; there is no penalty for being explicit.
Truthiness in @if(expression)
@if does not require the expression to be a comparison. It evaluates the expression and applies Nift's documented truthiness rules, so direct boolean/value conditions remain valid alongside arithmetic and comparisons.
@if(post.published){ ... }
@if(!site.disabled){ ... }
@if(loop.index % 2 == 0){ ... } See Loops & conditions → for the complete truthiness table and branch syntax.
Lazy ternary rendering
$[condition ? true-branch : false-branch] uses the same expression evaluator for its condition. The shorthand $[condition ? true-branch] is equivalent to an empty false branch.
$[score >= 50 ? 'pass' : 'fail']
$[loop.index % 2 == 0 ? ' even' : ' odd']
$[post.featured ? @input('partials/featured-badge.html')] Ternary branches are deliberately lazy and are rendered as ordinary Nift source. Only the selected branch is parsed; the unselected branch does not resolve values, execute directives, register dependencies/requirements or fail because of something that only exists in that branch.
Multiline values, injection and validation
@:=(items){
[1, 2, 3]
}
$[config := validate(schema, inject("data/config.nift"))] @:=(name){...} is the multiline form of declaration; its outer braces are framing, and its body is one expression. inject(path) parses another file as Nift expression source at that position, with its own child scope and automatic dependency tracking. validate(schema, value) returns the value unchanged when schema validation succeeds and fails the build otherwise.
Expressions inside other Nift features
Whenever an existing feature consumes a $[...] scalar, that scalar can be derived by an expression. Pagination also has explicit relative page syntax:
@pathtopage($[paginate.previous])
@pathtopage(+$[offset])
@pathtopage(-$[offset])
@pathtopage(+$[offset * 2]) @pathtopage(n) ultimately requires an integer page number or integer offset; expression support does not make fractional page numbers valid.