Dynamic ContentDynamic Data With Twig

Dynamic Data With Twig

How to use Twig expressions inside the Proton widget's HTML panel to output post data, apply filters, write conditions, and access site-wide values.

Twig is the expression language Protuno uses for dynamic data inside the HTML panel of a Proton widget. Write {{ post.title }} anywhere in the HTML and Protuno replaces it with the current post's title when the page renders. No PHP, no shortcodes, no custom fields plugin required for standard WordPress data.


Where To Write Twig

Open a Proton widget in Developer Mode (Style tab → Edit Code) and write Twig expressions directly in the HTML panel. Twig is processed server-side before the HTML reaches the browser.

<article>
  <h1>{{ post.title }}</h1>
  <p class="date">{{ post.date|date('F j, Y') }}</p>
  <div class="excerpt">{{ post.excerpt }}</div>
  <a href="{{ post.link }}">Read More</a>
</article>

Output Expressions: {{ … }}

Use double curly braces to output a value:

{{ post.title }}
{{ user.name }}
{{ site.name }}
{{ post.meta('price') }}

Values are HTML-escaped automatically. To output raw HTML (like post content that contains markup), use the raw filter:

{{ post.content|raw }}

The 7 Data Providers

Providers are the data sources available in Twig expressions. Each one represents a WordPress object in the current page context.

Free vs Pro: The free version only resolves post.title, post.content, post.link / post.url / post.permalink, and post.thumbnail / post.featured_image, plus the full image chain (src, alt, width, height). All other post fields (post.excerpt, post.date, post.author, post.categories, post.meta(), etc.) and the entire site, user, term, and request providers require Protuno Pro and return empty in Free builds.

post: Current Post (partial Free)

{{ post.title }}           {# Post title #}
{{ post.id }}              {# Post ID #}
{{ post.excerpt }}         {# Excerpt #}
{{ post.content|raw }}     {# Rendered content #}
{{ post.date }}            {# Published date (raw) #}
{{ post.link }}            {# Permalink #}
{{ post.slug }}            {# Post slug #}
{{ post.status }}          {# Post status #}
{{ post.comment_count }}   {# Number of comments #}
{{ post.meta('key') }}     {# Custom field value #}

{# Chained — author object #}
{{ post.author.name }}
{{ post.author.email }}
{{ post.author.avatar('96') }}

{# Chained — featured image #}
{{ post.thumbnail.src }}
{{ post.thumbnail.src('large') }}
{{ post.thumbnail.alt }}

{# Chained — first category #}
{{ post.categories.name }}

product: WooCommerce Product (extends post)

{{ product.price }}              {# Formatted price HTML (use |raw) #}
{{ product.regular_price }}
{{ product.sale_price }}
{{ product.is_on_sale }}         {# Boolean #}
{{ product.sku }}
{{ product.stock_status }}
{{ product.stock_quantity }}
{{ product.is_in_stock }}        {# Boolean #}
{{ product.add_to_cart_url }}
{{ product.average_rating }}
{{ product.review_count }}
{{ product.short_description|raw }}
{{ product.description|raw }}
{{ product.slug }}
{{ product.link }}
{{ product.thumbnail.src }}

user: Current Logged-in User (Pro)

{{ user.name }}          {# Display name #}
{{ user.id }}
{{ user.email }}         {# Admin context only #}
{{ user.bio|raw }}       {# Biography (HTML) #}
{{ user.url }}           {# Website URL #}
{{ user.link }}          {# Author archive URL #}
{{ user.avatar('64') }}  {# Avatar URL at given size in px #}
{{ user.meta('key') }}   {# User meta value #}

term: Current Term (Pro)

{{ term.name }}
{{ term.id }}
{{ term.slug }}
{{ term.description }}
{{ term.count }}         {# Number of posts #}
{{ term.link }}          {# Archive URL #}

image: Image Object

image is not used directly, it is returned by chained expressions like post.thumbnail:

{{ post.thumbnail.src }}
{{ post.thumbnail.src('thumbnail') }}
{{ post.thumbnail.src('medium') }}
{{ post.thumbnail.src('large') }}
{{ post.thumbnail.src('full') }}
{{ post.thumbnail.alt }}
{{ post.thumbnail.width }}
{{ post.thumbnail.height }}
{{ post.thumbnail.caption }}

site: Site-Wide Data (Pro)

{{ site.name }}
{{ site.description }}   {# Tagline #}
{{ site.url }}
{{ site.language }}
{{ site.logo.src }}      {# Site logo image #}
{{ site.icon.src }}      {# Site icon (favicon) #}
{{ site.option('key') }} {# wp_options value #}

request: URL Parameters (Pro)

{{ request.get('tab') }}   {# ?tab=value from URL #}
{{ request.url }}          {# Current page URL #}
{{ request.referrer }}     {# HTTP referrer #}

Filters: Transforming Values

Apply a filter with |filtername. Chain multiple filters with |filter1|filter2.

String Filters

{{ post.title|upper }}                   {# ALL CAPS #}
{{ post.title|lower }}                   {# all lowercase #}
{{ post.title|capitalize }}              {# First letter capitalized #}
{{ post.title|title }}                   {# Title Case #}
{{ post.excerpt|trim }}                  {# Remove leading/trailing whitespace #}
{{ post.excerpt|truncate(120) }}         {# Max 120 characters, ends with … #}
{{ post.excerpt|truncate(120, '...') }}  {# Custom suffix #}
{{ post.excerpt|excerpt(25) }}           {# Max 25 words #}
{{ post.content|striptags }}             {# Remove HTML tags #}
{{ post.title|slug }}                    {# url-safe-slug #}
{{ post.title|replace('Old', 'New') }}   {# Find and replace #}
{{ value|default('Fallback text') }}     {# Show fallback if empty #}
{{ post.content|wpautop|raw }}           {# Add paragraph tags (like WordPress) #}
{{ post.content|raw }}                   {# Output raw HTML #}

Number Filters

{{ product.regular_price|number_format(2) }}  {# 1,299.00 #}
{{ value|round(2) }}                           {# Round to 2 decimal places #}
{{ value|abs }}                                {# Absolute value #}
{{ product.price|currency|raw }}               {# WooCommerce price format #}

Date Filters

{{ post.date|date('F j, Y') }}      {# January 15, 2025 #}
{{ post.date|date('d/m/Y') }}       {# 15/01/2025 #}
{{ post.date|date('M Y') }}         {# Jan 2025 #}
{{ post.date|time_ago }}            {# "3 days ago" #}
{{ post.categories|first }}            {# First item #}
{{ post.categories|last }}             {# Last item #}
{{ post.categories|length }}           {# Count of items #}
{{ post.categories|reverse }}          {# Reversed array #}
{{ post.categories.name|join(', ') }}  {# "News, Updates, Reviews" #}

Conditions: {% if %}

Show content conditionally:

{% if post.thumbnail %}
  <img src="{{ post.thumbnail.src('large') }}" alt="{{ post.thumbnail.alt }}">
{% endif %}

{% if user.id %}
  <p>Welcome, {{ user.name }}!</p>
{% else %}
  <a href="/login">Log in</a>
{% endif %}

{% if product.is_on_sale %}
  <span class="badge">Sale</span>
{% endif %}

Structural Tags

Five <uichemy-*> tags render whole WordPress blocks. Place them directly in the HTML panel:

<header>
  <uichemy-site-logo></uichemy-site-logo>
  <nav><uichemy-nav-menu></uichemy-nav-menu></nav>
</header>

<main>
  <uichemy-post-content></uichemy-post-content>
  <uichemy-toc></uichemy-toc>
</main>
TagWhat it outputs
<uichemy-nav-menu>Active WordPress navigation menu
<uichemy-site-logo>Site logo linked to home
<uichemy-site-icon>Site icon linked to home
<uichemy-post-content>Current post content (the_content)
<uichemy-toc>Table of contents from post headings

Frequently Asked Questions

Twig runs server-side, does it affect page speed? Twig expressions are evaluated once per page request and cached in the rendered HTML. The overhead is minimal, comparable to a WordPress template tag.

Can I use {{ post.title }} inside a CSS value? Yes, in some contexts, for example, a dynamic CSS custom property: --bg-url: url('{{ post.thumbnail.src }}');. Use |esc_url to ensure the URL is safe.

Can I access ACF fields through Twig? ACF fields are available through {{ post.meta('field_name') }} (using the ACF field name or key). For complex ACF field types (gallery, repeater), use the ACF dynamic tags in the panel instead.

The |raw filter, is it safe? Only use |raw on values you trust (WordPress-generated content like post.content|raw). Never use |raw on user-input values.