Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

wire.filters

import wire

wire exposes this as wire.filters, so import wire is enough and the names are called as wire.filters.*. import wire.filters reaches 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