what

Variables & Filters

Render dynamic content with #variable# syntax. Apply filters to transform output.

Basic Syntax

Variables are wrapped in # hash marks. They're replaced with their value at render time.

<h1>Hello, #name#!</h1>
<p>Your email is #user.email#</p>

Use dot notation to access nested values:

#user.email#              <!-- object property -->
#order.items.0.name#      <!-- array index -->
#settings.theme.color#    <!-- deeply nested -->

Naming Rules

Variable names can contain letters, numbers, underscores, and dots (for nesting). They must start with a letter or underscore.

#my_variable#             <!-- valid -->
#user.first_name#         <!-- valid (dot = nesting) -->
#item_2#                  <!-- valid -->
#my-variable#             <!-- NOT valid (hyphens not allowed) -->
Rule of thumb: use snake_case for variable names — first_name, cart_count, is_admin.

Unresolved variables are preserved as-is in the output. They don't cause errors.

Special Variables

These variables are available automatically in every page:

PrefixDescriptionExample
user.*Authenticated user claims from JWT#user.email#, #user.full_name#, #user.authenticated#
session.*Server-side session state (reactive)#session.cart_count#
env.*Environment variables#env.API_URL#
query.*URL query parameters#query.page#, #query.search#
flash.*One-time flash messages (consumed on read)#flash.success#, #flash.error#
errors.*Form validation errors#errors.email#
nowCurrent date and time (ISO format)#now#, #now|date:"full"#
_csrfCSRF token for forms#_csrf#
Tip: Session variables are reactive. Any element displaying #session.x# updates automatically when the value changes via w-set.

Default Values

Use the default filter to provide a fallback when a variable is empty or undefined:

#user.name|default:"Guest"#
#query.sort|default:"newest"#
#session.theme|default:"light"#

Without a default, unresolved variables stay as literal #variable# text in the output.

Arithmetic

Use arithmetic operators directly inside #...# expressions:

#price * 0.21#            <!-- multiply -->
#session.age + 1#         <!-- add -->
#total - discount#        <!-- subtract -->
#count / 2#               <!-- divide -->
#2 + 3 * 4#               <!-- = 14 (precedence: * before +) -->

Arithmetic works in templates, session mutations, and <if> conditions:

<what>
session.tax = #session.price# * 0.21
session.total = #session.price# + #session.tax#
</what>

<p>Tax: #session.tax|round:2#</p>

Math Filters

FilterDescriptionExample
roundRound to nearest integer#tax|round#4
round:NRound to N decimal places#price * 0.21|round:2#2.10
ceilRound up#rating|ceil#4
floorRound down#rating|floor#3

Filters

Filters transform a variable's output. Add them after the variable name with a pipe |:

#name|uppercase#
#price|currency:"EUR"#
#bio|truncate:100#

Available Filters

FilterDescriptionExample
uppercaseConvert to uppercase#name|uppercase#ALICE
lowercaseConvert to lowercase#name|lowercase#alice
capitalizeCapitalize first letter#word|capitalize#Hello
titleTitle Case every word#text|title#Hello World
truncate:NTruncate to N characters#bio|truncate:50#
countString length#name|count#5
numberFormat with thousands separator#total|number#1,234
currencyFormat as currency (default USD)#price|currency#$9.99
currency:"EUR"Currency with code#price|currency:"EUR"#
dateFormat a date (default: medium)#created|date#Mar 15, 2025
date:"mask"Format with custom mask#created|date:"mmm d, yyyy"#
round:NRound to N decimal places#tax|round:2#2.10
ceilRound up to integer#rating|ceil#4
floorRound down to integer#rating|floor#3
jsonJSON-encode the value#data|json#
markdownRender Markdown to HTML#content|markdown#
rawOutput without HTML escaping#html_content|raw#
pluralizePluralize based on count#count|pluralize:"item","items"#
replaceReplace substring#text|replace:"old","new"#
slice:start:endSubstring extraction#text|slice:0:10#
default:"val"Fallback for empty values#name|default:"Anonymous"#

Date & Time Formatting

The date filter formats date strings using intuitive mask patterns. Use #now# to get the current date and time.

#now|date#                         <!-- Mar 7, 2026 -->
#now|date:"full"#                  <!-- Friday, March 7, 2026 -->
#now|date:"h:nn tt"#               <!-- 2:30 PM -->
#created|date:"mmm d, yyyy"#       <!-- Mar 15, 2025 -->
#event_date|date:"dddd, mmmm d"#   <!-- Saturday, March 15 -->

Date Masks

MaskOutputExample
dDay, no leading zero5
ddDay, leading zero05
dddAbbreviated day nameMon
ddddFull day nameMonday
mMonth, no leading zero3
mmMonth, leading zero03
mmmAbbreviated month nameMar
mmmmFull month nameMarch
yy2-digit year26
yyyy4-digit year2026

Time Masks

MaskOutputExample
h12-hour, no leading zero2
hh12-hour, leading zero02
H24-hour, no leading zero14
HH24-hour, leading zero14
nMinutes, no leading zero5
nnMinutes, leading zero05
sSeconds, no leading zero8
ssSeconds, leading zero08
tA or PP
ttAM or PMPM

Presets

Use a preset name instead of a mask for common formats:

PresetEquivalentExample Output
shortm/d/yy3/15/25
mediummmm d, yyyyMar 15, 2025
longmmmm d, yyyyMarch 15, 2025
fulldddd, mmmm d, yyyySaturday, March 15, 2025
timeh:nn tt2:30 PM
isoyyyy-mm-dd2025-03-15
#created|date:"short"#     <!-- 3/15/25 -->
#now|date:"time"#          <!-- 2:30 PM -->
#event|date:"iso"#         <!-- 2025-03-15 -->
Note: m/mm always means month. Use n/nn for minutes. This avoids ambiguity in combined date+time masks like "m/d/yyyy h:nn tt".

Accepted Input Formats

The date filter accepts these input formats:

  • 2025-03-15 — ISO date
  • 2025-03-15T14:30:00 — ISO datetime
  • 2025-03-15 14:30:00 — datetime with space
  • 2025-03-15T14:30:00Z — RFC 3339

Filter Chaining

Chain multiple filters with additional pipes. They apply left to right:

#bio|truncate:100|uppercase#
#user.name|default:"Guest"|capitalize#
#price|currency:"USD"|replace:"$","USD "#

The first filter receives the raw variable value. Each subsequent filter receives the output of the previous one.

HTML Escaping

All variable output is HTML-escaped by default. Characters like <, >, &, and " are converted to their HTML entities.

<!-- If name = "<script>alert('xss')</script>" -->
<p>#name#</p>
<!-- Renders: &lt;script&gt;alert('xss')&lt;/script&gt; -->

To output raw, unescaped HTML (for trusted content only), use the raw filter:

<div>#article.body|raw#</div>
Warning: Only use |raw with content you trust. Never use it with user-submitted data — it bypasses XSS protection.

Computed Variables

Define computed variables in the <what> block to create derived values using string interpolation:

site/profile.html
<what>
compute.greeting = "Hello, #user.name#!"
compute.profile_url = "/users/#user.id#/profile"
compute.full_title = "#title# | My Site"
</what>

<h1>#greeting#</h1>
<a href="#profile_url#">View profile</a>

Computed variables:

  • Are available as #name# (without the compute. prefix)
  • Can reference any variable available in the page context
  • Resolve in order, so later computed variables can reference earlier ones