wire.filters
import wire
wireexposes this aswire.filters, soimport wireis enough and the names are called aswire.filters.*.import wire.filtersreaches the same definitions directly.
The filters every Wire template starts with.
A filter takes the value on the left of the | as its first argument
and whatever the template passed as the rest, so the one written
price|round(2) is called as round(price, 2). An argument a template
leaves out arrives as nil, which is why nearly every filter here
states its default in its own doc block rather than insisting the
template spell it out.
Wire.register_filter() adds to this set and can replace anything in
it, so an application that wants date to mean its own thing is free to
say so.
On escaping
Almost every filter here returns a plain string, which Wire then escapes
for wherever it is going. The three that return markup instead are
raw, nl2br and json_script, and each of them says so and says what
it does about the text it was given. A filter that returns markup built
out of untrusted input is how a template engine becomes a security hole,
so the two that build markup escape their input first.
Constants
BUILTIN
wire.filters.BUILTIN = {...}
Every filter Wire starts with, keyed by the name a template calls it by.
Two of them are spelled differently here than the function is: Zuri
already has escape and default on other things, so the functions
carry longer names and the template-facing names live here.
Functions
raw()
wire.filters.raw(value) -> Safe
Marks a value as markup so Wire writes it out without escaping it.
This is the only way to get live HTML out of a variable, and it is a promise that the value is safe where it lands. Applying it to anything a user supplied is a cross-site scripting hole; applying it to markup your own code built is exactly what it is for.
<div class="body">{{ article.rendered_html|raw }}</div>
Parameters
value(any)
Returns Safe
escape_value()
wire.filters.escape_value(value, context) -> Safe
Escapes a value for a context other than the one it is being written into, or escapes a value that was already marked safe.
Wire escapes for the right context on its own, so this is only needed
when a value has to carry escaping it would not otherwise get: a URL
built into an ordinary attribute, say, or markup that reached the
template as Safe but should be shown rather than rendered.
context is 'text', 'attribute', 'url', 'script' or 'style'
and defaults to 'text'.
<code>{{ snippet|escape }}</code>
<a data-target="{{ link|escape('url') }}">go</a>
Parameters
value(any)context(?string)
Returns Safe
Raises ArgumentError when context is not one Wire knows.
upper()
wire.filters.upper(value) -> string
The value in upper case.
{{ code|upper }}
Parameters
value(any)
Returns string
lower()
wire.filters.lower(value) -> string
The value in lower case.
Parameters
value(any)
Returns string
title()
wire.filters.title(value) -> string
The value with the first letter of every word in upper case and the rest in lower case.
Words are separated by whitespace, so o'brien becomes O'brien rather
than O'Brien. Names are harder than a filter can be.
{{ 'jane IS here'|title }} {# Jane Is Here #}
Parameters
value(any)
Returns string
capitalize()
wire.filters.capitalize(value) -> string
The value with its first letter in upper case and the rest left alone.
Unlike title this touches nothing but the first character, which is
what a sentence wants.
{{ 'hello THERE'|capitalize }} {# Hello THERE #}
Parameters
value(any)
Returns string
trim()
wire.filters.trim(value) -> string
The value with leading and trailing whitespace removed.
Parameters
value(any)
Returns string
truncate()
wire.filters.truncate(value, length, suffix) -> string
The value cut down to length characters, with suffix put on the end when anything was actually cut.
length counts the characters kept, not including the suffix. suffix defaults to a single ellipsis. A value already short enough is returned untouched, suffix and all left off.
{{ article.summary|truncate(80) }}
{{ article.summary|truncate(80, ' [more]') }}
Parameters
value(any)length(number)suffix(?string)
Returns string
Raises ArgumentError when length is missing or negative.
replace()
wire.filters.replace(value, search, replacement) -> string
The value with every occurrence of search replaced by replacement.
search is matched literally, never as a regular expression, so a value
full of punctuation cannot turn into a pattern by accident.
replacement defaults to an empty string, which makes |replace(',') a
way to strip something out.
Parameters
value(any)search(string)replacement(?string)
Returns string
Raises ArgumentError when search is missing or empty.
lpad()
wire.filters.lpad(value, width, fill) -> string
The value padded on the left with fill until it is width characters long.
fill defaults to a space. A value already at least width long is returned untouched.
Parameters
value(any)width(number)fill(?string)
Returns string
Raises ArgumentError when width is missing or not a number.
rpad()
wire.filters.rpad(value, width, fill) -> string
The value padded on the right with fill until it is width characters long.
Parameters
value(any)width(number)fill(?string)
Returns string
Raises ArgumentError when width is missing or not a number.
repeat()
wire.filters.repeat(value, count) -> string
The value repeated count times.
A count of zero gives an empty string.
Parameters
value(any)count(number)
Returns string
Raises ArgumentError when count is missing or negative.
nl2br()
wire.filters.nl2br(value) -> Safe
The value with its line breaks turned into <br> elements.
The text is escaped first and the result is marked as markup, so this is
safe on anything a user typed. Both \n and \r\n count as a line
break.
<p>{{ comment.body|nl2br }}</p>
Parameters
value(any)
Returns Safe
strip_tags()
wire.filters.strip_tags(value) -> string
The value with every HTML tag removed, leaving only its text.
The markup is really parsed rather than pattern-matched away, so <p title="a>b">x</p>
gives x and not b">x. The result is plain text and is escaped like
anything else when it is written out.
<meta name="description" content="{{ article.body|strip_tags|truncate(150) }}">
Parameters
value(any)
Returns string
slug()
wire.filters.slug(value) -> string
The value as a lowercase, hyphen separated slug.
Runs of anything that is not a letter or a digit become a single hyphen, and hyphens at either end are trimmed. Non-ASCII letters are kept, so a slug can still be readable in a language that needs them.
<a href="/posts/{{ post.title|slug }}">{{ post.title }}</a>
Parameters
value(any)
Returns string
url_encode()
wire.filters.url_encode(value) -> string
The value percent encoded for use inside a URL.
Parameters
value(any)
Returns string
json()
wire.filters.json(value) -> string
The value as JSON.
The result is plain text, so writing it into a page escapes it like
anything else. Inside a <script> Wire already encodes every
interpolation as JSON, which makes this filter unnecessary there.
Parameters
value(any)
Returns string
json_script()
wire.filters.json_script(value, id) -> Safe
The value as JSON wrapped in a <script type="application/json">
element, ready to be read back by a script on the page.
id becomes the element’s id so the script can find it, and defaults to leaving the id off. This is the safe way to hand data to the browser: the JSON never touches an executable context, so no value in it can become code.
{{ initial_state|json_script('state') }}
<script>
var state = JSON.parse(document.getElementById('state').textContent)
</script>
Parameters
value(any)id(?string)
Returns Safe
abs()
wire.filters.abs(value) -> number
The value without its sign.
Parameters
value(any)
Returns number
Raises TypeError when the value is not a number.
round()
wire.filters.round(value, places) -> number
The value rounded to places decimal places.
places defaults to 0, which rounds to a whole number. Halves round
away from zero, so 2.5 becomes 3 and -2.5 becomes -3.
{{ order.total|round(2) }}
Parameters
value(any)places(?number)
Returns number
Raises TypeError when the value is not a number.
floor()
wire.filters.floor(value) -> number
The largest whole number at or below the value.
Parameters
value(any)
Returns number
Raises TypeError when the value is not a number.
ceil()
wire.filters.ceil(value) -> number
The smallest whole number at or above the value.
Parameters
value(any)
Returns number
Raises TypeError when the value is not a number.
number_format()
wire.filters.number_format(value, places, point, separator) -> string
The value written out with thousands separated and a fixed number of decimal places.
places defaults to 0, point to '.' and separator to ',',
which is the convention most of the English speaking world uses. Passing
the other two the other way round gives the European one.
{{ 1234567.891|number_format(2) }} {# 1,234,567.89 #}
{{ 1234567.891|number_format(2, ',', '.') }} {# 1.234.567,89 #}
Parameters
value(any)places(?number)point(?string)separator(?string)
Returns string
Raises TypeError when the value is not a number.
filesize()
wire.filters.filesize(value, binary) -> string
A byte count written the way a person reads it.
binary chooses the units: left out or false gives the decimal ones a
disk is sold in (kB of 1000 bytes), true gives the binary ones memory
is measured in (KiB of 1024). Values below a kilobyte are written as a
plain count of bytes.
{{ upload.size|filesize }} {# 1.4 MB #}
{{ upload.size|filesize(true) }} {# 1.3 MiB #}
Parameters
value(any)binary(?bool)
Returns string
Raises TypeError when the value is not a number.
length()
wire.filters.length(value) -> number
How many entries the value has.
Works on a string, a list, a dictionary or a byte string. nil has a
length of zero rather than being an error, so x-if="items|length"
reads correctly for a variable that was never supplied.
Parameters
value(any)
Returns number
Raises TypeError when the value is not something with a length.
first()
wire.filters.first(value) -> any
The first entry, or nil when there is none.
Parameters
value(any)
Returns any
Raises TypeError when the value cannot be indexed.
last()
wire.filters.last(value) -> any
The last entry, or nil when there is none.
Parameters
value(any)
Returns any
Raises TypeError when the value cannot be indexed.
join()
wire.filters.join(value, glue) -> string
The entries joined into one string with glue between them.
glue defaults to an empty string. Each entry is rendered the way it would be if it were written on its own, so a list of numbers joins without any ceremony.
{{ tags|join(', ') }}
Parameters
value(any)glue(?string)
Returns string
Raises TypeError when the value is not a sequence.
sort()
wire.filters.sort(value, key) -> list
The entries in ascending order.
key names a field to sort by, for a list of dictionaries or instances; leaving it out sorts the entries themselves. Sorting is stable, and the original is not changed.
<li x-for="users|sort('name')" x-value="user">{{ user.name }}</li>
Parameters
value(any)key(?string)
Returns list
Raises TypeError when the value is not a sequence.
reverse()
wire.filters.reverse(value) -> any
The entries in the opposite order, or a string backwards.
Parameters
value(any)
Returns any
Raises TypeError when the value is not a sequence.
unique()
wire.filters.unique(value) -> list
The entries with later duplicates removed, keeping the first of each.
Parameters
value(any)
Returns list
Raises TypeError when the value is not a sequence.
keys()
wire.filters.keys(value) -> list
A dictionary’s keys, in insertion order.
Parameters
value(any)
Returns list
Raises TypeError when the value is not a dictionary.
values()
wire.filters.values(value) -> list
A dictionary’s values, in insertion order.
Parameters
value(any)
Returns list
Raises TypeError when the value is not a dictionary.
slice()
wire.filters.slice(value, start, end) -> any
The entries from start up to but not including end.
end defaults to the end of the sequence. A negative position counts back from the end, and a range that falls outside the sequence gives back whatever part of it does overlap rather than raising.
<li x-for="posts|slice(0, 5)" x-value="post">{{ post.title }}</li>
Parameters
value(any)start(number)end(?number)
Returns any
Raises TypeError when the value is not a sequence.
sum()
wire.filters.sum(value, key) -> number
The entries added together.
key names a field to add up, for a list of dictionaries or instances.
An empty sequence sums to 0.
<p>Total: {{ items|sum('price')|number_format(2) }}</p>
Parameters
value(any)key(?string)
Returns number
Raises TypeError when an entry is not a number.
split()
wire.filters.split(value, separator) -> list
The value split into a list on separator.
separator is matched literally and defaults to whitespace, which splits on any run of it.
Parameters
value(any)separator(?string)
Returns list
default_to()
wire.filters.default_to(value, fallback) -> any
fallback when the value is falsy, otherwise the value.
Falsy here is Wire’s falsy: nil, false, zero, an empty string and an
empty collection. A negative number is not falsy and is kept. Use ??
in an expression when only a missing value should fall back and a zero
should stand.
<p>{{ user.nickname|default('friend') }}</p>
Parameters
value(any)fallback(any)
Returns any
empty()
wire.filters.empty(value) -> bool
Whether the value has nothing in it.
nil is empty, an empty string and an empty collection are empty, and a
number never is. That last part is what separates this from plain
falsiness: 0|empty is false where 0 on its own is falsy.
<p x-if="results|empty">Nothing found.</p>
Parameters
value(any)
Returns bool
is()
wire.filters.is(value, expected) -> bool
Whether the value equals expected.
Kept from the first version of Wire, where it was the only way to
compare anything. x-if="status == 'active'" says the same thing and
reads better, so prefer that in new templates.
Parameters
value(any)expected(any)
Returns bool
not()
wire.filters.not(value, expected) -> bool
Whether the value differs from expected.
Parameters
value(any)expected(any)
Returns bool
date()
wire.filters.date(value, format) -> string
A date written out with the given format.
The value may be a date.Date, a Unix timestamp in seconds, or a string
in any of the formats date.parse() understands. format uses the same
directives as Date.format() and defaults to 'Y-m-d H:i:s'.
<time datetime="{{ post.created|date('Y-m-d') }}">
{{ post.created|date('jS F Y') }}
</time>
Parameters
value(any)format(?string)
Returns string
Raises TypeError when the value is not something Wire can read as
a date.
2026, Richard Ore and The Zuri Contributors