Plang reference
Filters
Every Plang filter with its arguments, a description and an example with its output: text, numbers, lists and Plangku's own.
A filter changes a value: {{ 'kopi' | upcase }}. Chain them left to right ({{ name | strip | capitalize }}), and give arguments after a colon (truncate: 30). Named arguments are written name: value, like icon: class: 'size-5'. See language for the syntax.
Rules that apply to every filter:
- Filters are forgiving about missing values: a
nilbecomes empty text and a bad number becomes0. - A filter's result is escaped when written out, except for
icon,markdownandnewline_to_br, which return markup the platform built. - Lists a filter produces are capped at 5000 items (
split,paragraphs,lines,concat). - An unknown filter name is a save-time error.
- Filters can't be used inside
ifconditions.assignthe result first.
Outputs below are shown as they appear on the page. The examples assume the website's language is Indonesian (id) and its WhatsApp number is 6281234567890.
Text filters
| Filter | Description | Example | Output |
|---|---|---|---|
append: text | Adds text at the end | {{ 'kopi' | append: ' susu' }} | kopi susu |
prepend: text | Adds text at the start | {{ 'susu' | prepend: 'es ' }} | es susu |
upcase | Capital letters | {{ 'kopi' | upcase }} | KOPI |
downcase | Small letters | {{ 'KOPI' | downcase }} | kopi |
capitalize | First letter big, the rest small | {{ 'kopi SUSU' | capitalize }} | Kopi susu |
strip | Removes spaces and line breaks at both ends | {{ ' halo ' | strip }} | halo |
lstrip, rstrip | The same, at the start or at the end only | {{ ' halo' | lstrip }} | halo |
strip_newlines | Removes line breaks | {{ text | strip_newlines }} | Text on one line |
strip_html | Removes <tags>, keeps the text | {{ '<p>Halo <b>dunia</b></p>' | strip_html }} | Halo dunia |
replace: from, to | Replaces every occurrence | {{ 'a-b-c' | replace: '-', '+' }} | a+b+c |
replace_first: from, to | Replaces the first occurrence only | {{ 'a-b-c' | replace_first: '-', '+' }} | a+b-c |
remove: text | Removes every occurrence | {{ 'a-b-c' | remove: '-' }} | abc |
remove_first: text | Removes the first occurrence | {{ 'a-b-c' | remove_first: '-' }} | ab-c |
truncate: length, ellipsis | Cuts text to length characters, the ellipsis included. Default length 50, default ellipsis … | {{ 'Kopi Susu Gula Aren' | truncate: 12 }} | Kopi Susu G… |
truncatewords: count, ellipsis | Keeps the first count words (default 15) | {{ 'Kopi Susu Gula Aren' | truncatewords: 2 }} | Kopi Susu… |
split: separator | Text to a list | {{ 'a,b,c' | split: ',' | join: ' / ' }} | a / b / c |
paragraphs | Splits on blank lines: one list item per paragraph | {{ s.text | paragraphs | size }} | 2 (for two paragraphs) |
lines | Splits into non-empty lines, each trimmed | {{ s.address | lines | join: ', ' }} | Jl. Melati 5, Bandung (for two lines) |
escape | HTML-escapes text. Output is escaped anyway; escape marks the result as done, so nothing is escaped twice | Not needed | |
escape_once | Escapes text that is not escaped yet | Rarely needed | |
url_encode | For use inside a link | {{ 'kopi susu & gula' | url_encode }} | kopi%20susu%20%26%20gula |
url_decode | The opposite | {{ 'kopi+susu%21' | url_decode }} | kopi susu! |
newline_to_br | Line breaks become <br>. The text is escaped first, and the result is markup | {{ s.address | newline_to_br }} | Jl. Melati 5<br>Bandung |
handleize (alias handle) | A URL-friendly slug: lowercase, no accents, hyphens, at most 80 characters | {{ 'Kopi Gayo & Aren!' | handleize }} | kopi-gayo-aren |
pluralize: one, many | one when the number is exactly 1, otherwise many | {{ 2 | pluralize: 'cup', 'cups' }} | cups |
default: fallback | fallback when the value is nil, false, empty text or an empty list. 0 is kept | {{ s.note | default: 'Tidak ada catatan' }} | Tidak ada catatan (if empty) |
json | The value as JSON text (escaped on output, so use it for data attributes) | {{ 'Hai' | json }} | "Hai" |
date: format | Formats a date. See the table below | {{ '2026-11-08' | date: '%A, %e %B %Y' }} | Minggu, 8 November 2026 |
default takes one named argument: allow_false: true keeps a real false instead of replacing it: {{ flag | default: 'x', allow_false: true }}.
paragraphs and split return lists, which a for loop can't filter directly. Assign first:
{% assign paras = s.text | paragraphs %}
{% for p in paras %}<p>{{ p }}</p>{% endfor %}Date formats
date accepts a text like 2026-11-08 or a full ISO date (2026-11-08T14:05:09Z), a number (seconds, or milliseconds when very large), or the words now and today. Dates are in UTC. Text it can't read is returned unchanged. Without a format it uses %e %B %Y. Month and day names follow the language of the page: Minggu and November in Indonesian, Sunday and November in English.
| Token | Meaning | Example |
|---|---|---|
%Y, %y | Year, four or two digits | 2026, 26 |
%m | Month number with a leading zero | 11 |
%d, %e | Day with a leading zero, or without | 08, 8 |
%B, %b | Month name, long or short | November, Nov |
%A, %a | Weekday name, long or short | Minggu, Min |
%H, %M, %S | Hours (24 hour), minutes, seconds | 14, 05, 09 |
%% | A literal percent sign | % |
{{ '2026-11-08' | date: '%d/%m/%Y' }}
Hak cipta {{ 'now' | date: '%Y' }}Number filters
Numbers are read the way prices are written in Indonesia: '12' is 12, 'Rp 25.000' is 25000 (dots between groups of three digits are thousands), '1.500.000,50' is 1500000.5 and '1,5' is 1.5. '1.5' and '25,000.50' work too. Anything unreadable is 0.
| Filter | Description | Example | Output |
|---|---|---|---|
plus: n | Add | {{ 10 | plus: 5 }} | 15 |
minus: n | Subtract | {{ 10 | minus: 3 }} | 7 |
times: n | Multiply | {{ 4 | times: 2.5 }} | 10 |
divided_by: n | Divide. When both numbers are whole, the result is rounded down. Dividing by 0 gives 0 | {{ 10 | divided_by: 4 }} | 2 |
modulo: n | Remainder. Modulo 0 gives 0 | {{ 10 | modulo: 3 }} | 1 |
round: digits | Rounds (default 0 digits, at most 10) | {{ 3.14159 | round: 2 }} | 3.14 |
ceil, floor | Round up, round down | {{ 3.2 | ceil }} | 4 |
abs | Without the minus sign | {{ -4 | abs }} | 4 |
at_least: n | The bigger of the value and n | {{ 2 | at_least: 5 }} | 5 |
at_most: n | The smaller of the value and n | {{ 9 | at_most: 5 }} | 5 |
divided_by rounds down only when both numbers are whole: {{ 10 | divided_by: 4 }} is 2, {{ 7.5 | divided_by: 2 }} is 3.75. 4.0 counts as a whole number, so to get a fraction from whole numbers multiply instead: {{ 10 | times: 0.25 }} is 2.5. A number is written with at most 10 decimals.
List filters
Most also work on text, or turn a single value into a one-item list. key can be a dotted path such as 'price.value'.
| Filter | Description | Example | Output |
|---|---|---|---|
size | Number of items, or of characters in text | {{ s.items | size }} | 3 |
first, last | The first or last item (or character) | {{ s.items | first | map: 'name' }} | Sari |
join: separator | List to text. Default separator is a space | {{ list | join: ', ' }} | a, b, c |
reverse | Reverse a list, or text | {{ 'abc' | reverse }} | cba |
sort: key | Ascending. Numbers numerically, text by character code (capitals first). key is optional | {{ s.plans | sort: 'price' | map: 'name' | join: ',' }} | Hemat,Usaha,Bisnis |
sort_natural: key | Ignores capitals and sorts numbers inside text naturally (item2 before item10) | {{ words | sort_natural | join: ',' }} | a,a,b,B |
uniq | Removes duplicates | {{ words | uniq | join: ',' }} | b,a,B |
compact | Removes nil and empty text | {{ list | compact | size }} | 2 |
concat: list | Joins two lists | {{ a | concat: b | size }} | 5 |
map: key | One property from every item | {{ s.items | map: 'title' | join: ', ' }} | Kopi, Teh |
where: key, value | Items whose property equals value (compared as text). Without a value, items whose property is true | {{ s.plans | where: 'featured' | size }} | 1 |
sum: key | Adds numbers, or a property of each item | {{ s.plans | sum: 'price' }} | 1250000 |
slice: start, length | A piece of a list or text. Negative start counts from the end. Default length 1 | {{ 'Kopi Susu' | slice: -4, 4 }} | Susu |
{% assign popular = s.plans | where: 'featured' %}
{% assign cheapest = s.plans | sort: 'price' | first %}
Mulai dari {{ cheapest.price | money }}Plangku filters
link
{{ value | link }} turns a stored link (wa:Halo, page:tentang, #kontak, tel:..., mailto:..., a web address) into a link object. It prints the address and has .href, .kind and .external. Empty values stay empty.
{% assign to = 'wa:Halo, mau tanya' | link %}
<a href="{{ to }}">{{ to.kind }}</a>Output: <a href="https://wa.me/6281234567890?text=Halo%2C%20mau%20tanya">whatsapp</a>. {{ 'page:' | link }} is the home page.
whatsapp_url
{{ 'Halo, mau pesan' | whatsapp_url }} gives https://wa.me/6281234567890?text=Halo%2C%20mau%20pesan: a chat with the business, using the number from the website settings, with the text typed in. It returns plain text (an address), not a link object. In translated versions the message you wrote in a template stays as written; messages stored in link settings are translated.
money
{{ 25000 | money }} gives Rp 25.000. A number, or text made of digits and dots ('25.000', where dots are thousands separators), is formatted in rupiah. Any other text is returned unchanged. Collection item prices (item.price) are already formatted, so only use money on numbers from your own settings.
icon
{{ 'Truck' | icon }} draws an icon as inline SVG that takes the colour of the text (currentColor).
| Argument | Meaning |
|---|---|
class: 'size-6 text-s-hl' | CSS classes for the <svg>. Without a size class a line icon has width and height 24. Only letters, digits and _ : @ / . [ ] % # ( ) ! - and spaces are kept |
stroke: 1.5 | Line thickness for line icons, above 0 up to 4. Default 2 |
fallback: 'Star' | The icon to draw when the name is empty or unknown |
{{ 'ShoppingBag' | icon: class: 'size-5', stroke: 1.75 }}
{{ s.icon | icon: class: 'card-icon', fallback: 'Sparkles' }}
{{ 'whatsapp' | icon: class: 'size-5' }}Names are the ones in the editor's icon picker (about 220 line icons, for example Truck, ShieldCheck, MessageCircle, ShoppingBag, Star, Phone, MapPin, Check, ArrowRight, Menu). Both ShoppingBag and shopping-bag work. A name that isn't an icon draws nothing unless you give a fallback.
Brand names are matched first, in lowercase, and draw a filled logo instead of a line icon: instagram, tiktok, facebook, youtube, whatsapp, x, threads, shopee, gojek, grab. Brand icons ignore stroke, and without a class they are size-5.
{{ 'x' | icon }} is the X (Twitter) logo and {{ 'X' | icon }} is a close cross.t
{{ 'Lihat semua' | t }} writes the text in the visitor's language. See translations. The text should be a literal in the template so Plangku can collect it. Without a translation the original text is shown.
ui
{{ 'contact' | ui }} writes a word the platform already knows in the page's language (Kontak in Indonesian, Contact in English). Use one of the keys below; an unknown key is written out as it is. Placeholders such as {brand} are not filled in, so madeWith, order and captchaLabel are not very useful in themes.
| Key | Indonesian | English |
|---|---|---|
all | Semua | All |
menu | Menu | Menu |
contact | Kontak | Contact |
rights | Hak cipta dilindungi. | All rights reserved. |
madeWith | Dibuat dengan {brand} | Made with {brand} |
pages | Halaman | Pages |
categories | Kategori | Categories |
mainMenu | Menu utama | Main menu |
openMenu | Buka menu | Open menu |
noMatch | Belum ada yang cocok. | Nothing matches yet. |
map | Peta lokasi | Location map |
language | Bahasa | Language |
order | Halo {site}, saya mau pesan {item}{price}. Masih ready? | Hi {site}, I’d like to order {item}{price}. Is it available? |
cookieNotice | Website ini memakai cookie untuk mengukur kunjungan dan iklan. | This website uses cookies to measure visits and ads. |
cookieAccept | Terima | Accept |
cookieDecline | Tolak | Decline |
The form messages are also available: formRequired, formEmail, formPhone, formNumber, formDate, formTime, formChoice, formSelect, formCheck, captchaLabel, captchaHelp, captchaWrong, formRetry, formBusy, formFailed, formGone, formAnother, formWhatsapp, formWhatsappText and formPreview. Themes can't build forms (see security), so you will rarely need them.
markdown
{{ s.text | markdown }} turns Markdown text into safe HTML. The text is escaped first, and only these things are produced: ## and ### headings, - bullets, 1. numbered lists, > quotes, ---, **bold**, *italic*, [links](https://...) and .
{{ '**Gratis** ongkir untuk [semua kota](/produk)' | markdown }}Output: <p><strong>Gratis</strong> ongkir untuk <a href="/produk">semua kota</a></p>
section_vars
{{ 'brand' | section_vars }} returns the colour variables of a section background (base, surface, brand or inverse; anything else means base) as text for a style attribute: --s-bg, --s-fg, --s-muted, --s-line, --s-card, --s-hl, --s-hl-fg, --s-btn, --s-btn-fg and --s-btn-soft. Use it to give one block inside a section a different background than the section itself. See styling.
<div class="band" style="{{ 'inverse' | section_vars }}">...</div>.band { background: var(--s-bg); color: var(--s-fg); }asset_url
{{ 'logo.png' | asset_url }} is the address of an image uploaded with a theme package (assets/logo.png). With or without the assets/ prefix. Unknown files give empty text. Only image files can be assets (png, jpg, jpeg, webp, gif, avif).
<img src="{{ 'logo.png' | asset_url }}" alt="{{ site.name }}">youtube_id
{{ 'https://youtu.be/dQw4w9WgXcQ' | youtube_id }} gives dQw4w9WgXcQ. It understands watch?v=, youtu.be/, shorts/, embed/ and live/ addresses, and returns empty text for anything else. Use it for an embed:
{% assign id = s.video | youtube_id %}
{% if id != blank %}
<iframe src="https://www.youtube-nocookie.com/embed/{{ id }}" title="Video" loading="lazy" allowfullscreen></iframe>
{% endif %}image_url
{{ s.photo | image_url }} returns the address when it is an uploaded image (/uploads/...) or an https:// address, and empty text otherwise (http:, javascript: and everything else are dropped). Image settings are already filtered this way. Use it for a collection item's image or any text you built yourself.
Not in Plang
If a filter isn't listed on this page it doesn't exist. Commonly missed ones from Liquid are money_with_currency, img_url, base64_encode, md5, find and reject. where only compares for equality, and t takes no variables, so build the sentence from settings or from several t texts instead.