PEL function reference
Every function callable inside a PEL expression, verified against crates/pgdl/src/pel/functions.rs and crypto.rs. Functions receive already-evaluated arguments; stochastic ones draw from a fresh per-occurrence seed, so two calls in one template differ yet stay reproducible.
For the readable guide, see Expressions (PEL); for operators and coercion, see PEL operators.
Argument coercion
Two coercions govern how arguments are read:
- String coercion (most string functions):
nullbecomes the empty string, booleans and numbers render naturally (integral numbers without a decimal point), arrays and objects render as JSON. - Numeric coercion (arithmetic functions
add/subtract/…): a number stays itself; a boolean is0/1; a string is parsed, defaulting to0when it is not a number; anything else is0. (This is the function-argument rule. The+ - * /operators are stricter — see PEL operators.)
A result that is a whole number is narrowed to an integer. Every function checks its arity; a wrong count is a BadParams error, never a panic.
Conditional
| Function | Arity | Returns | Description |
|---|---|---|---|
if(cond, a, b) | 3 | any | a if cond is truthy, else b. |
coalesce(…) | 1+ | any | The first non-null argument, else null. |
default(v, fallback) | 2 | any | v unless it is empty (null or ""), then fallback. |
optional(v) | 1 | any | "" when v is null, else v. |
switch is a special expression form (not a plain function): switch(subject, key: value, …, default: value). The subject is stringified and matched against each arm key; an unmatched subject with no default arm is an error.
if(age >= 18, 'adult', 'minor') → 'adult' (age = 40)
coalesce(null, '', 'x') → ''
default('', 'n/a') → 'n/a'
switch(tier, gold: 3, silver: 2, default: 0) → 3 (tier = 'gold')Case transforms
All take one string argument and return a string.
| Function | Description | Example input → output |
|---|---|---|
lowercase(s) | Lowercase. | 'AbC' → 'abc' |
uppercase(s) | Uppercase. | 'AbC' → 'ABC' |
capitalize(s) | First letter upper, rest lower. | 'hELLO' → 'Hello' |
titlecase(s) | Capitalize each word, space-joined. | 'hello world' → 'Hello World' |
pascalcase(s) | Capitalize each word, no separator. | 'hello world' → 'HelloWorld' |
camelcase(s) | First word lower, rest capitalized. | 'hello world' → 'helloWorld' |
snakecase(s) | Lowercase words joined with _. | 'Hello World' → 'hello_world' |
kebabcase(s) | Lowercase words joined with -. | 'Hello World' → 'hello-world' |
Words are split on non-alphanumeric separators and camelCase humps.
String
| Function | Arity | Returns | Description |
|---|---|---|---|
trim(s) | 1 | string | Strip leading/trailing whitespace. |
reverse(s) | 1 | string | Reverse by Unicode scalar. |
length(v) | 1 | number | Array length, or character count of a string. |
slugify(s) | 1 | string | Lowercase; runs of non-alphanumerics become single -; trimmed. |
repeat(s, n) | 2 | string | s repeated n times (n < 0 → 0). |
truncate(s, n, suffix?) | 2–3 | string | First n chars; append suffix only if truncated. |
substring(s, start, len?) | 2–3 | string | len chars from start (to the end when len omitted); clamped. |
pad(s, width, ch?) | 2–3 | string | Left-pad to width with ch (default space). No-op if already ≥ width. |
padRight(s, width, ch?) | 2–3 | string | Right-pad to width with ch. |
replace(s, from, to) | 3 | string | Replace all occurrences of from. |
concat(…) | 0+ | string | Concatenate all arguments (string-coerced). |
split(s, sep) | 2 | array | Split on sep; an empty sep splits into characters. |
join(arr, sep) | 2 | string | Join array elements with sep. |
contains(s, needle) | 2 | boolean | Whether s contains needle. |
startsWith(s, p) / starts_with | 2 | boolean | Prefix test. |
endsWith(s, p) / ends_with | 2 | boolean | Suffix test. |
indexOf(s, needle) / index_of | 2 | number | Character index of needle, or -1. |
slugify('Merhaba Dünya!') → 'merhaba-dünya'
truncate('hello world', 5, '…') → 'hello…'
substring('abcdef', 1, 3) → 'bcd'
pad('7', 3, '0') → '007'
split('a,b,c', ',') → ['a','b','c']
concat('id-', 42) → 'id-42'Pattern fills
Each replaces the placeholders in its own symbol class with random characters (other characters pass through; a backslash escapes the next character). See the inline pattern grammar for the full symbol table.
| Function | Arity | Fills | Description |
|---|---|---|---|
numerify(s) | 1 | # % | Digits (% = non-zero digit). |
letterify(s) | 1 | ! @ ? | Letters (! upper, @ lower, ? any). |
hexify(s) | 1 | ^ _ | Hex (^ upper, _ lower). |
alphanumerify(s) | 1 | all classes | Every placeholder in any class. |
numerify('+90 5## ### ## ##') → '+90 542 187 63 09'
letterify('??-###') → 'Kp-###'Numeric
Arithmetic functions coerce arguments to numbers (see Argument coercion).
| Function | Arity | Returns | Description |
|---|---|---|---|
add(a, b) | 2 | number | a + b. |
subtract(a, b) | 2 | number | a - b. |
multiply(a, b) | 2 | number | a * b. |
divide(a, b) | 2 | number | a / b; error on divide by zero. |
modulo(a, b) | 2 | number | a % b; error on modulo by zero. |
round(x, places?) | 1–2 | number | Round to places decimals (default 0). |
floor(x) | 1 | number | Round toward −∞. |
ceil(x) | 1 | number | Round toward +∞. |
abs(x) | 1 | number | Absolute value. |
min(a, b) | 2 | number | Smaller of two. |
max(a, b) | 2 | number | Larger of two. |
clamp(x, lo, hi) | 3 | number | Constrain x to [lo, hi]. |
uniform(a, b) | 2 | number | Random float in [a, b] (stochastic). |
pow(x, y) | 2 | number | x raised to y. |
sqrt(x) | 1 | number | Square root (of max(x, 0)). |
log(x, base?) | 1–2 | number | Logarithm; natural log when base omitted. |
sign(x) | 1 | number | -1, 0, or 1. |
round(3.14159, 2) → 3.14
clamp(120, 0, 100) → 100
divide(10, 4) → 2.5List
| Function | Arity | Returns | Description |
|---|---|---|---|
pick(arr) | 1 | any | A uniform-random element (a non-array returns itself; empty → null). |
first(arr) | 1 | any | First element, or the value itself; null if empty. |
last(arr) | 1 | any | Last element, or the value itself; null if empty. |
nth(arr, i) | 2 | any | Element at index i (null if out of range). |
sum(arr) | 1 | number | Sum of the elements (numeric-coerced). |
cycle(arr) | 1 | any | Element at row_index mod len — deterministic round-robin by output row. |
sample(arr, count?) | 1–2 | array | count distinct random elements without replacement. count is a number or an "a-b" range; default 1. |
pick_weighted(arr) | 1 | any | Weighted pick; elements are { value, weight } (missing weight = 1, 0 = never). All-zero weights error. |
pick(['a','b','c']) → 'b'
cycle(['Mon','Tue','Wed']) → 'Wed' (row index 2)
sample([1,2,3,4,5], 2) → [4, 1]
pick_weighted([{value:'x',weight:9},{value:'y',weight:1}]) → 'x'Masking / privacy
| Function | Arity | Returns | Description |
|---|---|---|---|
redact(x?) | 0–1 | string | Always "[REDACTED]". |
mask(s, show?, side?) | 1–3 | string | Replace all but show characters with * (default show = 4). side "start" keeps the first show; otherwise the last show. |
mask('4111111111111234') → '************1234'
mask('secrettoken', 3, 'start') → 'sec********'Formatting
| Function | Arity | Returns | Description |
|---|---|---|---|
format(v, spec?) | 1–2 | string | Format v per spec (see below). |
spec is kind or kind:arg:
spec | Result |
|---|---|
decimal:P | Fixed to P decimals (default 2). |
percent | v × 100 with two decimals and a %. |
number | Rounded integer with thousands separators. |
currency:CODE | Thousands-grouped with two decimals and a symbol: TRY → ₺, EUR → €, GBP → £, anything else → $. |
filesize | Human byte size (B/KB/MB/GB/TB). |
| (other / omitted) | The value, string-coerced. |
format(1234567, 'number') → '1,234,567'
format(0.185, 'percent') → '18.50%'
format(4200, 'currency:TRY') → '₺4,200.00'
format(1536, 'filesize') → '1.5 KB'Dates
Dates are ISO strings in, ISO strings out (UTC). Parsing rejects out-of-range values (2024-02-31 is not silently normalized).
| Function | Arity | Returns | Description |
|---|---|---|---|
addDays(date, n) | 2 | date | Shift by n days. |
subtractDays(date, n) | 2 | date | Shift back n days. |
addMonths(date, n) | 2 | date | Shift by n months (day clamped to month length). |
addYears(date, n) | 2 | date | Shift by n years. |
startOfMonth(date) | 1 | date | First day of the month. |
endOfMonth(date) | 1 | date | Last day of the month. |
startOfYear(date) | 1 | date | January 1 of the year. |
dateDiff(a, b, unit?) | 2–3 | number | b − a in unit. |
dateDiff units: hours, days (default), months, years. months and years are approximations (30-day months, 365-day years), not calendar-aware. Any other unit (including seconds) yields the raw difference in seconds.
addDays('2024-01-30', 5) → '2024-02-04'
addMonths('2024-01-31', 1) → '2024-02-29'
endOfMonth('2024-02-10') → '2024-02-29'
dateDiff('2024-01-01', '2024-03-01', 'days') → 60Reference clock
Deterministic when a reference instant is frozen (via --reference-time or the registry). See Determinism.
| Function | Arity | Returns | Description |
|---|---|---|---|
now() | 0 | datetime | The reference instant as an ISO datetime. |
today() | 0 | date | The reference instant's date. |
age(birthdate) | 1 | number | Whole years from birthdate to the reference date. |
age('1990-07-01') → 36 (reference date 2026-07-06, before birthday)Crypto / privacy
Standard algorithms, so output is identical across runtimes.
| Function | Arity | Returns | Description |
|---|---|---|---|
hash(s, algo?) | 1–2 | string | Hex digest. algo is sha256 (default), sha1, or md5; an unknown algorithm errors. |
hmac(data, key) | 2 | string | HMAC-SHA256, hex-encoded. |
base64(s) | 1 | string | Standard base64 of the UTF-8 bytes. |
urlEncode(s) | 1 | string | Percent-encode per RFC 3986 (unreserved set kept). |
anonymize(s, mode?) | 1–2 | string | A consistent pseudonym — the same input always maps to the same token (referential integrity survives). mode "domain" keeps an email's domain and fakes only the local part. |
hash('secret') → '2bb80d537b1da3e3…' (sha256, hex)
hash('secret', 'md5') → '5ebe2294ecd0e0f0…'
anonymize('user@acme.com', 'domain') → 'a1b2c3d4e5@acme.com'Inline generators
Inside a template, a placeholder whose body starts with a reserved keyword is an inline generator rather than an expression. The reserved heads are number, pattern, date, datetime, time, uuid, ulid, nanoid. Each occurrence draws a fresh sub-seed, so repeated inline generators in one template differ. The forms below are shown inside a template's double braces.
number
{{ number:MIN-MAX }} integer if both bounds are integers
{{ number:MIN-MAX:PRECISION }} float rounded to PRECISION decimals
{{ number:MIN-MAX:step:STEP }} draw from MIN, MIN+STEP, … ≤ MAXIf either bound has a decimal point the result is a float; otherwise it is an integer. PRECISION applies only to the float form. STEP is at least 1.
{{ number:1-100 }} → 42
{{ number:0-1:2 }} → 0.37
{{ number:0-100:step:5 }} → 65pattern
A pattern:SPEC inline generator replaces placeholder characters with random ones; a backslash escapes the next character (kept literal). Other characters pass through.
| Symbol | Draws from |
|---|---|
# | digit 0–9 |
% | non-zero digit 1–9 |
! | uppercase letter |
@ | lowercase letter |
? | any letter |
^ | hex digit, uppercase |
_ | hex digit, lowercase |
* | alphanumeric |
{{ pattern:###-##-#### }} → 271-04-8853
{{ pattern:!!##@@ }} → QX47kddate / datetime / time
{{ date:START..END }} random ISO date (YYYY-MM-DD)
{{ datetime:START..END }} random ISO datetime (…T…Z)
{{ time:START..END }} random HH:MM:SSdate and datetime bounds accept ISO-8601 instants or relative specs resolved against the reference clock: now, today, and offsets like -1year, +7days, now-24h, today+1month. time bounds are HH:MM:SS or HH:MM. A trailing :FORMAT tag on the range is ignored (output is always ISO); because times legitimately contain :, a fully parseable end bound always wins over format-stripping.
Relative-offset units: s/sec/secs/second/seconds, m/min/mins/minute/minutes, h/hour/hours, d/day/days, w/week/weeks, month/months, y/year/years.
{{ date:2020-01-01..2024-12-31 }} → 2022-08-14
{{ datetime:-30days..now }} → 2026-06-19T11:42:07Z
{{ time:09:00..17:00 }} → 13:27:41id generators
{{ uuid }} UUID v4 (seed-derived)
{{ uuid:v7 }} UUID v7 (synthetic, index-sortable timestamp)
{{ ulid }} 26-char Crockford base32 ULID
{{ nanoid }} 21-char Nano ID
{{ nanoid:LEN }} Nano ID of length LENuuid (bare or uuid: with anything other than v7) is v4; only uuid:v7 selects v7. All are pure functions of the seed (v7's timestamp is the frozen reference instant plus the invocation index — sortable yet reproducible, never wall-clock).
{{ uuid }} → 3f2504e0-4f89-41d3-9a0c-0305e82c3301
{{ nanoid:10 }} → V1StGXR8_Z