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 nil becomes empty text and a bad number becomes 0.
  • A filter's result is escaped when written out, except for icon, markdown and newline_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 if conditions. assign the 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

FilterDescriptionExampleOutput
append: textAdds text at the end{{ 'kopi' | append: ' susu' }}kopi susu
prepend: textAdds text at the start{{ 'susu' | prepend: 'es ' }}es susu
upcaseCapital letters{{ 'kopi' | upcase }}KOPI
downcaseSmall letters{{ 'KOPI' | downcase }}kopi
capitalizeFirst letter big, the rest small{{ 'kopi SUSU' | capitalize }}Kopi susu
stripRemoves spaces and line breaks at both ends{{ ' halo ' | strip }}halo
lstrip, rstripThe same, at the start or at the end only{{ ' halo' | lstrip }}halo
strip_newlinesRemoves line breaks{{ text | strip_newlines }}Text on one line
strip_htmlRemoves <tags>, keeps the text{{ '<p>Halo <b>dunia</b></p>' | strip_html }}Halo dunia
replace: from, toReplaces every occurrence{{ 'a-b-c' | replace: '-', '+' }}a+b+c
replace_first: from, toReplaces the first occurrence only{{ 'a-b-c' | replace_first: '-', '+' }}a+b-c
remove: textRemoves every occurrence{{ 'a-b-c' | remove: '-' }}abc
remove_first: textRemoves the first occurrence{{ 'a-b-c' | remove_first: '-' }}ab-c
truncate: length, ellipsisCuts text to length characters, the ellipsis included. Default length 50, default ellipsis …{{ 'Kopi Susu Gula Aren' | truncate: 12 }}Kopi Susu G…
truncatewords: count, ellipsisKeeps the first count words (default 15){{ 'Kopi Susu Gula Aren' | truncatewords: 2 }}Kopi Susu…
split: separatorText to a list{{ 'a,b,c' | split: ',' | join: ' / ' }}a / b / c
paragraphsSplits on blank lines: one list item per paragraph{{ s.text | paragraphs | size }}2 (for two paragraphs)
linesSplits into non-empty lines, each trimmed{{ s.address | lines | join: ', ' }}Jl. Melati 5, Bandung (for two lines)
escapeHTML-escapes text. Output is escaped anyway; escape marks the result as done, so nothing is escaped twiceNot needed
escape_onceEscapes text that is not escaped yetRarely needed
url_encodeFor use inside a link{{ 'kopi susu & gula' | url_encode }}kopi%20susu%20%26%20gula
url_decodeThe opposite{{ 'kopi+susu%21' | url_decode }}kopi susu!
newline_to_brLine 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, manyone when the number is exactly 1, otherwise many{{ 2 | pluralize: 'cup', 'cups' }}cups
default: fallbackfallback 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)
jsonThe value as JSON text (escaped on output, so use it for data attributes){{ 'Hai' | json }}"Hai"
date: formatFormats 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:

plang
{% 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.

TokenMeaningExample
%Y, %yYear, four or two digits2026, 26
%mMonth number with a leading zero11
%d, %eDay with a leading zero, or without08, 8
%B, %bMonth name, long or shortNovember, Nov
%A, %aWeekday name, long or shortMinggu, Min
%H, %M, %SHours (24 hour), minutes, seconds14, 05, 09
%%A literal percent sign%
plang
{{ '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.

FilterDescriptionExampleOutput
plus: nAdd{{ 10 | plus: 5 }}15
minus: nSubtract{{ 10 | minus: 3 }}7
times: nMultiply{{ 4 | times: 2.5 }}10
divided_by: nDivide. When both numbers are whole, the result is rounded down. Dividing by 0 gives 0{{ 10 | divided_by: 4 }}2
modulo: nRemainder. Modulo 0 gives 0{{ 10 | modulo: 3 }}1
round: digitsRounds (default 0 digits, at most 10){{ 3.14159 | round: 2 }}3.14
ceil, floorRound up, round down{{ 3.2 | ceil }}4
absWithout the minus sign{{ -4 | abs }}4
at_least: nThe bigger of the value and n{{ 2 | at_least: 5 }}5
at_most: nThe 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'.

FilterDescriptionExampleOutput
sizeNumber of items, or of characters in text{{ s.items | size }}3
first, lastThe first or last item (or character){{ s.items | first | map: 'name' }}Sari
join: separatorList to text. Default separator is a space{{ list | join: ', ' }}a, b, c
reverseReverse a list, or text{{ 'abc' | reverse }}cba
sort: keyAscending. Numbers numerically, text by character code (capitals first). key is optional{{ s.plans | sort: 'price' | map: 'name' | join: ',' }}Hemat,Usaha,Bisnis
sort_natural: keyIgnores capitals and sorts numbers inside text naturally (item2 before item10){{ words | sort_natural | join: ',' }}a,a,b,B
uniqRemoves duplicates{{ words | uniq | join: ',' }}b,a,B
compactRemoves nil and empty text{{ list | compact | size }}2
concat: listJoins two lists{{ a | concat: b | size }}5
map: keyOne property from every item{{ s.items | map: 'title' | join: ', ' }}Kopi, Teh
where: key, valueItems whose property equals value (compared as text). Without a value, items whose property is true{{ s.plans | where: 'featured' | size }}1
sum: keyAdds numbers, or a property of each item{{ s.plans | sum: 'price' }}1250000
slice: start, lengthA piece of a list or text. Negative start counts from the end. Default length 1{{ 'Kopi Susu' | slice: -4, 4 }}Susu
plang
{% assign popular = s.plans | where: 'featured' %}
{% assign cheapest = s.plans | sort: 'price' | first %}
Mulai dari {{ cheapest.price | money }}

Plangku filters

{{ 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.

plang
{% 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).

ArgumentMeaning
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.5Line thickness for line icons, above 0 up to 4. Default 2
fallback: 'Star'The icon to draw when the name is empty or unknown
plang
{{ '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.

Note: lowercase names are looked up as brands first and PascalCase names are always icons, so {{ '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.

KeyIndonesianEnglish
allSemuaAll
menuMenuMenu
contactKontakContact
rightsHak cipta dilindungi.All rights reserved.
madeWithDibuat dengan {brand}Made with {brand}
pagesHalamanPages
categoriesKategoriCategories
mainMenuMenu utamaMain menu
openMenuBuka menuOpen menu
noMatchBelum ada yang cocok.Nothing matches yet.
mapPeta lokasiLocation map
languageBahasaLanguage
orderHalo {site}, saya mau pesan {item}{price}. Masih ready?Hi {site}, I’d like to order {item}{price}. Is it available?
cookieNoticeWebsite ini memakai cookie untuk mengukur kunjungan dan iklan.This website uses cookies to measure visits and ads.
cookieAcceptTerimaAccept
cookieDeclineTolakDecline

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 ![photos](https://...).

plang
{{ '**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.

plang
<div class="band" style="{{ 'inverse' | section_vars }}">...</div>
css
.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).

plang
<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:

plang
{% 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.