Liquid Templating
All HTML in PageWeave supports Liquid. Use it for dynamic content, loops, conditionals, and data table access.
Variables
Global Drops
-
site.name— Website name -
site.subdomain— Website subdomain -
site.domain— Custom domain (if set) -
site.language— BCP 47 language tag (from website setting) -
site.indexable— Search engine indexing enabled -
site.tables.{slug}— Data table rows -
page.title— Page title -
page.path— Page URL path -
page.language— Page-level language override (BCP 47 tag) -
page.indexable— Whether search engines index this page
Template Pages
When a page is associated with a table, the row variable is available. Every placeholder in the page's path maps verbatim to a row data field (uniform field semantics):
row.id— Row UUIDrow.slug— theslugdata field's value (alias forrow.data.slug). You supply it in row data — no auto-generation.row.created_at— Creation timestamprow.updated_at— Last update timestamprow.url— Full URL to this row's page, in its collapsed canonical form (default-locale segments omitted, e.g./docs/seonot/docs/en/seo)row.{field}— Any table fieldrow.{field}.{nested}— Nested data
Syntax
{{ site.name }}
{% if site.indexable %}
<meta name="robots" content="index, follow">
{% endif %}
{% for i in (1..3) %}{{ i }}{% endfor %}
{% assign x = "value" %}
Filters
Standard Liquid filters: upcase, downcase, date, truncate, times, divided_by, append, prepend, strip_html.
Table filters:
where: "field", "value"— Filter rowssort_by: "field", "desc"— Sort rows (numeric fields sort numerically)
Filters chain on {% assign %} — assign the filtered collection to a variable first, then loop over it:
{% assign posts = site.tables.blog_posts | where: "published", "true" | sort_by: "created_at", "desc" %}
{% for post in posts %}
<h2>{{ post.title }}</h2>
{% endfor %}
Note: Liquid ignores filter chains written directly inside a
{% for %}tag ({% for post in site.tables.blog_posts | where: ... %}renders every row, unfiltered, with no error). Page validation flags this pattern with a warning pointing to the assign-first form above.
Markdown Rendering
The markdownify filter renders markdown as HTML — safe by default (literal raw HTML in the source is neutralized), with the full GFM set: tables, task lists, strikethrough, autolinks, and soft line breaks.
<div class="prose">{{ row.content | markdownify }}</div>
Use it to render markdown stored in data tables (e.g. documentation bodies, blog post content) inside template pages.
Data Tables
Basic Loop
{% for post in site.tables.blog_posts limit: 10 %}
<h2>{{ post.title }}</h2>
<p>{{ post.excerpt | truncate: 150 }}</p>
{% endfor %}
Pagination
{% paginate site.tables.blog_posts by 10 order_by: "created_at" order_dir: "desc" where: "published:true" %}
{% for post in site.tables.blog_posts %}
{{ post.title }}
{% endfor %}
{% if paginate.next %}
<a href="{{ paginate.next.url }}">Next</a>
{% endif %}
{% endpaginate %}
Default limit: 20 rows. Hard cap: 100.
Template Page Example
<article>
<h1>{{ row.title }}</h1>
<p>Published: {{ row.created_at | date: "%B %d, %Y" }}</p>
<div>{{ row.body | markdownify }}</div>
<a href="{{ row.url }}">Permalink</a>
</article>
Syntax Warnings
Liquid syntax errors are reported via liquid_warnings in page responses. Validation warnings (including filter chains inside {% for %} tags) appear in validation.warnings when creating or updating pages.