This document provides a complete reference for all PAD pipe functions.
Pipe functions transform values using the | operator. A field tag pipes as it stands;
a literal or an expression goes through {echo}:
{$name | upper} # a field tag pipes
{echo $name | upper} # the same value, printed raw - {echo} skips the sanitize chain
{echo $text | trim | lower} # Chained functions
{echo $date | date('Y-m-d')} # With parameters
{echo 'hello' | upper} # a literal needs {echo}
Multiple functions can be chained:
{echo $name | trim | upper | left(10)}
Where nothing is piped in - at the head of an expression, or as an argument of another
call - a function written as a call reads its first argument as the value, as PHP’s
functions do; date keeps PHP’s order, the format first and then the moment:
{if left($code, 2) eq 'NL'} ... {/if}
{echo 'Hello ' . upper($name)}
{echo $text | replace('x', upper($y))}
{echo date('Y', $created)}
Functions that extract parts of a string based on a delimiter.
| Function | Parameters | Description |
|---|---|---|
after |
delimiter | Returns everything after the first occurrence of delimiter |
afterLast |
delimiter | Returns everything after the last occurrence of delimiter |
before |
delimiter | Returns everything before the first occurrence of delimiter |
beforeLast |
delimiter | Returns everything before the last occurrence of delimiter |
{echo 'hello/world/test' | after('/')} → 'world/test'
{echo 'hello/world/test' | afterLast('/')} → 'test'
{echo 'hello/world/test' | before('/')} → 'hello'
{echo 'hello/world/test' | beforeLast('/')} → 'hello/world'
All four skip the whole delimiter, multi-character or not, and give the value back unchanged
when it does not contain the delimiter. An empty delimiter is found everywhere: first at the
start - after('') is the whole value, before('') is empty - and last at the end -
afterLast('') is empty, beforeLast('') is the whole value.
Functions that extract substrings by position.
| Function | Parameters | Description |
|---|---|---|
substr |
start [, length] | mb_substr() - extract substring from a 0-based position, counted in characters |
left |
count | Returns the first N characters |
right |
count | Returns the last N characters |
mid |
start, length | Returns substring starting at position (1-based index) |
{echo 'Hello World' | left(5)} → 'Hello'
{echo 'Hello World' | right(5)} → 'World'
{echo 'Hello World' | mid(7, 5)} → 'World'
{echo 'Hello World' | substr(6)} → 'World'
{echo 'Hello World' | substr(0, 5)} → 'Hello'
A count of zero or less names no characters: left(0), right(0) and the negative counts
all answer the empty string. mid reads a start below 1 as 1 (its documented base), a
negative or zero length as no characters, and no length at all as the rest of the value.
substr alone keeps PHP’s negative-offset semantics - use it when counting from the end is
what is meant.
Functions that change the case of text.
| Function | Parameters | Description |
|---|---|---|
upper |
- | Converts to uppercase, letter by letter in UTF-8 (café → CAFÉ) |
lower |
- | Converts to lowercase, letter by letter in UTF-8 (ÉCOLE → école) |
capitalize |
- | Capitalizes first letter of each word, in UTF-8 (éric → Éric) - PHP’s ucwords knows ASCII only |
ucwords |
- | Alias for capitalize |
{echo 'hello world' | upper} → 'HELLO WORLD'
{echo 'HELLO WORLD' | lower} → 'hello world'
{echo 'hello world' | capitalize} → 'Hello World'
Functions that modify string content.
| Function | Parameters | Description |
|---|---|---|
trim |
- | Removes whitespace from both ends (PHP trim) |
replace |
search, replace | Replaces all occurrences of search with replace |
cut |
text | Removes all occurrences of text (replace with empty) |
white |
- | Normalizes whitespace - collapses multiple spaces to single space |
{echo ' hello ' | trim} → 'hello'
{echo 'hello world' | replace('world', 'there')} → 'hello there'
{echo 'hello world' | cut('o')} → 'hell wrld'
{echo 'hello world' | white} → 'hello world'
Functions for encoding and escaping text for various contexts.
| Function | Parameters | Description |
|---|---|---|
html |
- | Escapes HTML special characters (PHP htmlspecialchars) |
sanitize |
- | Full special character sanitization (FILTER_SANITIZE_FULL_SPECIAL_CHARS) |
url |
- | URL-encodes the value (PHP urlencode) - a space becomes + |
slashes |
- | Adds backslashes before quotes (PHP addslashes) - not SQL escaping: give values to db() as placeholders |
stripslashes |
- | Removes backslashes (PHP stripslashes) |
encodeHigh |
- | Encodes high ASCII characters (>127) |
stripLow |
- | Strips low ASCII control characters |
escape |
strategy | Escapes for a context: html (default), attr, js, css, url - see below |
slug |
separator | A readable URL part: 'Crème Brûlée & Co.' → 'creme-brulee-co' |
ignore |
- | Escapes PAD syntax characters ({ } \| = , @) so the output is not parsed as PAD tags - not HTML escaping: in an attribute use html first |
{echo '<script>' | html} → '<script>'
{echo 'hello world' | url} → 'hello+world'
{echo "it's here" | slashes} → "it\'s here"
{json 'products' | ignore} → an application's own _tags/json.php (CLAUDE.md): the JSON it HTML-escaped itself, kept from the PAD parser
{echo $json | html | ignore} → any value: html makes it safe inside an attribute, ignore keeps PAD off it
{echo 'Crème Brûlée & Co.' | slug} → 'creme-brulee-co'
escape(strategy) writes the value for the context it lands in:
| Strategy | For | Example output for it's </b> |
|---|---|---|
html (default) |
text and quoted attributes | it's </b> |
attr |
any attribute, even unquoted | it's </b> |
js |
inside a JavaScript string | it\x27s\x20\x3C\x2Fb\x3E |
css |
a CSS value | it\27 s\20 \3C \2F b\3E |
url |
one path or query part (rawurlencode) |
it%27s%20%3C%2Fb%3E |
<script>var name = "{$name | escape('js')}";</script>
The sanitize chain a {$field} ends with never encodes an entity twice, and the js, css
and url forms leave nothing for it to change, so the two do not stack.
Functions that add HTML formatting to text.
| Function | Parameters | Description |
|---|---|---|
bold |
- | Wraps value in <b> tags |
nbsp |
- | Replaces spaces with |
markdown |
- | Reads the value as Markdown and writes it as HTML - raw HTML escaped, javascript: links dropped |
{echo 'important' | bold} → '<b>important</b>'
{echo 'hello world' | nbsp} → 'hello world'
{echo '**Hi** <b>' | markdown} → '<p><strong>Hi</strong> <b></p>'
{$post.body | markdown} shows a post kept in a database. The HTML is safe by construction - the
raw HTML of the value is escaped, a link whose URL names a scheme other than http, https,
mailto, ftp or tel keeps only its text - so a field whose last pipe is markdown skips the
sanitize chain, which would otherwise escape the markup just made. The same subset as the
{markdown} tag (TAGS.md); the tag’s html option, which lets raw HTML through, is for the
template’s own text and has no pipe form. A value stays text: {php:getcwd} in a post is shown.
{$query | highlight('sql')} shows a value as source code, coloured - pad, php, html,
css, js, json, yaml, sql, bash or text. Every piece of the value is escaped, so a
field whose last pipe is highlight skips the sanitize chain, as one ending in markdown
does. The same block as the {highlight} tag (TAGS.md), without its lines and mark.
Functions that limit or control string length.
| Function | Parameters | Description |
|---|---|---|
max_len |
length | Truncates string to maximum length |
truncate |
length, ellipsis | Shortens to at most length characters, ellipsis (default …) included, ending on a whole word |
bytes |
precision | A byte count for people: 1536 → 1.5 KB (units of 1024, default 2 decimals) |
{echo 'Hello World' | max_len(5)} → 'Hello'
{echo 'Hi' | max_len(5)} → 'Hi'
{echo 'The quick brown fox jumps over the lazy dog' | truncate(20)} → 'The quick brown fox…'
{echo 3221225472 | bytes} → '3 GB'
Functions that test values and return boolean results (returns '1' for true, '' for
false). The one exception is exists, which answers '0' rather than '' for a missing
file.
| Function | Parameters | Description |
|---|---|---|
contains |
needle | Returns TRUE if value contains the needle string |
in |
values… | Returns '1' if value is in the list of parameters |
matches |
regex | Regular expression test, '1' or ''; a backslash in a PAD string is written doubled - '/x\\.y/' - and a {n} quantifier would open a tag, so repeat the class or pass the pattern from PHP |
like |
pattern | SQL LIKE pattern matching (% = any chars, _ = single character, counted as UTF-8 characters rather than bytes) |
between |
min, max | Returns TRUE if value is exclusively between min and max |
range |
min, max | Returns TRUE if value is inclusively in range (min <= value <= max) |
exists |
- | Returns '1' if file exists in APP directory, '0' otherwise |
{echo 'hello world' | contains('world')} → TRUE
{$status | in('active', 'pending')} → '1' or ''
{echo 'test.txt' | like('%.txt')} → '1'
{$age | between(17, 66)} → TRUE if 17 < age < 66
{$score | range(0, 100)} → TRUE if 0 <= score <= 100
{echo 'templates/page.php' | exists} → '1' or '0'
{$code | matches('/^[A-Z][A-Z][0-9]+$/')} → '1' or ''
The tests read infix in a condition as well - the value on the left is their input:
{if $status in ('active', 'pending')} ... {/if}
{if $email matches '/@example\\.com$/'} ... {/if}
{if $name like 'A%'} ... {/if}
{if $age range (18, 65)} ... {/if}
A not between the value and the test denies it, as SQL writes it:
{orders where='$status not in ("cancelled", "refunded")'} ... {/orders}
{if $name not like 'A%'} ... {/if}
default(fallback) answers the value, or the fallback when it is empty - '', NULL, FALSE
or an empty list (0 is a value). A field piped into it may be missing under the strict check:
{$title | default('Untitled')}
It is the function form of the ?? operator: {$title | ?? 'Untitled'}.
The like function supports SQL-style wildcards:
| Pattern | Meaning |
|---|---|
% |
Matches any sequence of characters |
_ |
Matches any single character |
\% |
Literal percent sign |
\_ |
Literal underscore |
\\ |
Literal backslash |
Inside a quoted pattern in a template the backslash is itself escaped, so a literal percent
sign is written \\% there - like('%\\%') - while a pattern that comes from the page’s
PHP, like($pattern), is written as is.
{echo 'filename.txt' | like('%.txt')} → '1'
{echo 'test123' | like('test___')} → '1'
{echo '100%' | like('%\\%')} → '1'
The request’s locale is $padLocale; $padLocales lists the ones the application speaks,
and with any listed, ?lang= (kept in the padLang cookie) or Accept-Language picks among
them. $padTimezone sets the timezone. Formatting uses PHP’s intl extension when it is there.
| Function | Parameters | Description |
|---|---|---|
trans |
count | The value as a key of the _lang/ catalogs, translated; the count picks the plural form |
currency |
code, locale | An amount as money the locale’s way - code defaults to EUR |
localDate |
date, time, locale | A date the locale’s way: styles none, short, medium (default), long, full, or an ICU pattern as the first argument; an empty style is the default (medium date, none time) |
{echo 'cart.title' | trans} → 'Je winkelwagen' in nl
{echo 'cart.items' | trans($n)} → '3 artikelen'
{$price | currency('USD', 'en_US')} → '$1,234.50'
{$created | localDate('long')} → 'October 5, 2026'
{$created | localDate('d MMMM y', 'none', 'nl')} → '5 oktober 2026'
| Function | Parameters | Description |
|---|---|---|
lookup |
set, key, field | The field of the row in another data set whose key equals the value - a join between lists from different sources |
The set is named as a data tag names its rows: a {data} block, a stored sequence, an array
of the page or of an enclosing row, or a _data/ file. key defaults to id; without a
field the answer is the row’s first field other than the key. The first matching row
counts; no match answers '', so | ?? 'fallback' supplies one. The set is indexed on the
key once per request - not once per row - and indexed again only when the set itself
changed. Strict mode names a lookup without a set, or with a name nothing holds.
{orders}
{$number}: {echo $customer_id | lookup('customers', 'id', 'name')}
{/orders}
{$customer_id | lookup('customers')} → the first field after id
{$code | lookup('countries', 'code', 'label') | ?? 'unknown'} → a miss with a fallback
{$id | lookup('customers', 'id', 'country') | lookup('countries', 'code', 'label')}
Functions for working with dates and timestamps.
| Function | Parameters | Description |
|---|---|---|
now |
- | Returns current Unix timestamp |
date |
[format [, modifier]] | Formats a timestamp using PHP date format |
time |
[format [, modifier]] | Alias for date |
timestamp |
[format [, modifier]] | Alias for date |
$padFmtDate global formatstrtotime modifier (e.g., ‘+1 day’)The value is a Unix timestamp, a date in text or a date object - a DateTime the page’s PHP
left (date, localDate and ago take it, as {timeago} and {countdown} do). A date object
no pipe turned into text is written Y-m-d H:i:s: {$created}.
{now} → current timestamp (e.g., 1702483200)
{now | date} → formatted with default format
{now | date('Y-m-d')} → '2024-12-13'
{now | date('Y-m-d', '+1 week')} → '2024-12-20'
{$timestamp | time('H:i:s')} → '14:30:00'
| Char | Description | Example |
|---|---|---|
Y |
4-digit year | 2025 |
m |
Month (01-12) | 03 |
d |
Day (01-31) | 15 |
H |
Hour (00-23) | 14 |
i |
Minutes (00-59) | 30 |
s |
Seconds (00-59) | 45 |
D |
Day name (short) | Mon |
l |
Day name (full) | Monday |
M |
Month name (short) | Mar |
F |
Month name (full) | March |
Important: Arithmetic pipes require a space between the operator and operand!
| Function | Description | Example |
|---|---|---|
+ n |
Add | {echo $value \| + 1} |
- n |
Subtract | {echo $value \| - 5} |
* n |
Multiply | {echo $value \| * 2} |
/ n |
Divide | {echo $value \| / 4} |
{echo $value | + 1} # Correct - adds 1
{echo $value | * 2} # Correct - multiplies by 2
{echo $value | +1} # WRONG - no space!
Use printf format specifiers for number formatting:
{echo $nbr | %.5f} # 5 decimal places: 3.14159
{echo $nbr | %'.09d} # Zero-padded to 9 digits: 000000123
{echo $nbr | %d} # Integer: 42
{echo $nbr | %x} # Hexadecimal: 2a
{echo $nbr | %05d} # Zero-padded to 5 digits: 00042
{echo $nbr | %+d} # With sign: +42
A format is one pipe segment like any other, anywhere in the chain - it runs to the next
|: {echo $price | * 1.21 | %.2f | . ' EUR'}. A % that opens no conversion is the
modulo operator: {echo $n | % 3}.
| Specifier | Description |
|---|---|
%d |
Integer |
%f |
Float |
%.Nf |
Float with N decimals |
%s |
String |
%x |
Hexadecimal (lowercase) |
%X |
Hexadecimal (uppercase) |
%0Nd |
Zero-padded integer |
%+d |
Signed integer |
The @ symbol represents the current value in expressions:
{echo 50 | @ * 4} # 200 (@ = 50)
{echo $text | '"' . @ . '"'} # Wrap value in quotes
{echo $num | @ + @ * 2} # Triple the value
Functions for working with PAD template syntax.
| Function | Parameters | Description |
|---|---|---|
open |
- | Wraps value in { and } to create a PAD tag |
close |
- | Wraps value in {/ and } to create a closing PAD tag |
tag |
- | Alias for open |
optional |
- | Returns value or empty string if null (null coalescing) |
{echo 'myTag' | open} → '{myTag}'
{echo 'myTag' | close} → '{/myTag}'
{$maybeNull | optional} → value or ''
The template side of some of the PHP helpers (HELPERS.md).
abbreviate(precision) writes a large number short for people - padNumberAbbreviate in a
template. K, M, B, T and Q stand for thousand up to quadrillion; the precision (0 unless
given) is the most decimals written, the trailing zeros dropped, and the unit is chosen on the
number as it will be written, so 999999 is 1M. Below 1000 the number has no letter, a
negative one keeps its sign, and a value that is not a number goes through unchanged. A
negative precision is reported.
{$visits | abbreviate} → '2M' ($visits = 1534200)
{$visits | abbreviate(1)} → '1.5M'
{$visits | abbreviate(2)} → '1.53M'
{echo 950 | abbreviate} → '950'
{echo -2500 | abbreviate(1)} → '-2.5K'
ordinal writes a whole number as its English place - padNumberOrdinal in a template: st,
nd and rd after 1, 2 and 3, th after the rest and after 11, 12 and 13, in 111 to 113 too. A
number with a fraction is reported; a value that is not a number goes through unchanged.
{runners}{$place | ordinal}: {$name} {/runners} → '1st: Ann 2nd: Bob 3rd: Cee '
{echo 22 | ordinal} → '22nd'
{echo 112 | ordinal} → '112th'
ago(from) says how long ago a moment was, in words - the padAgo helper
(HELPERS.md) in a template:
| Function | Parameters | Description |
|---|---|---|
ago |
[from] | The age of a timestamp or date in words, counted from now - or from from |
{$created | ago} → 'just now', '5 minutes ago', 'yesterday', '3 weeks ago'
{$deadline | ago} → 'in 2 hours', 'tomorrow', 'in 3 days'
{$start | ago($end)} → counted from $end instead of now
{echo '2026-10-04' | ago} → 'yesterday' on 5 October 2026
The value is a Unix timestamp, a date in text or a date object; now is the one padNowFreeze fixed, when a
page froze the clock. Under ten seconds is just now, then seconds, minutes and hours; from a
day on the calendar counts - yesterday, days, weeks, months, years. An empty value answers
empty; a value that is no date answers empty too, and the strict check names it.
after, afterLast, before, beforeLast
substr, left, right, mid
upper, lower, capitalize, ucwords
trim, replace, cut, white
html, sanitize, url, escape, slug, slashes, stripslashes, encodeHigh, stripLow, ignore
bold, nbsp, markdown
abbreviate, ordinal
max_len, truncate, bytes
contains, in, like, matches, between, range, exists
default
lookup
trans, currency, localDate
now, date, time, timestamp, ago
+, -, *, /
%d, %f, %.Nf, %s, %x, %X, %0Nd, %+d
open, close, tag, optional
Pipes can be applied to a tag at two points, and they do not act on the same text.
Transforms the content template, once, before any occurrence is rendered. What the function is handed is the source between the tags, not the tag’s data:
{items | trim}
<li>x</li>
{/items}
It is not a way to reorder or filter what a tag iterates - a field written inside would be transformed with everything else and then not resolve. Sorting is an option:
{items sort}
<li>{$name}</li>
{/items}
Transforms the output, after every occurrence has been rendered and joined:
{message}
Content: {$message}
{/message | upper}
Closing pipes chain left to right, each over what the one before returned.
Create custom functions in _functions/:
_functions/money.php:
<?php
return '$' . number_format($padContent, 2);
?>
Use in templates:
{echo $price | money} # $1,234.56
_functions/initials.php:
<?php
$words = explode(' ', $padContent);
$initials = '';
foreach ($words as $word) {
$initials .= strtoupper($word[0]);
}
return $initials;
?>
Use in templates:
{echo $name | initials} # "John Doe" → "JD"
Each function file receives these variables:
| Variable | Description |
|---|---|
$padContent |
The input value being piped |
$parm |
Array of parameters passed to the function |
$count |
Number of parameters in $parm |