This page summarizes the main syntax of filters, functions, and control structures available in Pebble templating.

Syntax Reference

There are two primary delimiters used within a Pebble template: {{ ... }} and {% ... %}.

{{ ... }} is used to output the result of an expression. Expressions can be very simple (ex. a variable name) or much more complex.

{% ... %} is used to change the control flow of the template; it can contain an if-statement, define a parent template, define a new block, etc.

If needed, you can escape those delimiters and avoid Pebble templating applying on a variable thanks to the raw tag.

You can use the dot (.) notation to access child attributes. If the attribute contains a special character, you can use the subscript notation ([]) instead.

{{ }} # Attribute 'bar' of 'foo'
{{ }} # Attribute 'baz' of attribute 'bar' of 'foo'
{{ foo['foo-bar'] }} # Attribute 'foo-bar' of 'foo'
{{ foo['foo-bar']['foo-baz'] }} # Attribute 'foo-baz' of attribute 'foo-bar' of 'foo'

If foo would be a List, then foo[idx] can be used to access the elements of index idx of the foo variable.


You can use filters in your expressions using a pipe symbol (|). Multiple filters can be chained, and the output of one filter is applied to the next one.

{{ "When life gives you lemons, make lemonade." | upper | abbreviate(13) }}

The above example will output the following:



Whereas filters are intended to modify existing content/variables, functions are intended to generate new content. Similar to other programming languages, functions are invoked via their name followed by parentheses function_name().

{{ max(user.score, highscore) }}

The above example will output the maximum of the two variables user.score and highscore.

Control Structure

Pebble provides several tags to control the flow of your template, two of the main ones being the for loop, and the if statement.

{% for article in articles %}
    {{ article.title }}
{% else %}
    "There are no articles."
{% endfor %}
{% if category == "news" %}
    {{ news }}
{% elseif category == "sports" %}
    {{ sports }}
{% else %}
    "Please select a category"
{% endif %}


Macros are lightweight and re-usable template fragments. A macro is defined via the macro tag:

{% macro input(type, name) %}
    {{ name }} is of type {{ type }}
{% endmacro %}

And the macro will be invoked just like a function:

{{ input("text", "Mitchell") }}

A macro does not have access to the main context; the only variables it can access are its local arguments.

Here is an example flow showing the usage of macro:

id: macro-example
namespace: dev

  - id: log_output
    type: io.kestra.plugin.core.log.Log
    message: |
      {% macro input(type, name) %}{{ name }} is of type {{ type }}{% endmacro %}
      {{ input("text", "John") }}
      {{ input("number", 1) }}

Named Arguments

In filters, functions, or macros, you can use named arguments. Named arguments allow us to be more explicit on which arguments are passed and avoid mandating to pass default values.

{{ stringDate | date(existingFormat="yyyy-MMMM-d", format="yyyy/MMMM/d") }}

Positional arguments can be used in conjunction with named arguments, but all positional arguments must come before any named arguments:

{{ stringDate | date("yyyy/MMMM/d", existingFormat="yyyy-MMMM-d") }}

Macros are a great use case for named arguments because they also allow you to define default values for unused arguments:


{% macro input(type="text", name, value) %}
    type is "{{ type }}", name is "{{ name }}", and value is "{{ value }}"
{% endmacro %}

{{ input(name="country") }}

{# will output: type is "text", name is "country", and value is "" #}


You add comments using the {# ... #} delimiters. These comments will not appear in the rendered output.

{% for article in articles %}
  {{ article.title }} has content {{ article.content }}
{% endfor %}


The simplest form of expressions are literals. Literals are representations for Java types such as strings and numbers.

  • "Hello World": Everything between two double or single quotes is a string. You can use a backslash to escape quotation marks within the string.
  • "Hello #{who}": String interpolation is also possible using #{}. In this example, if the value of the variable who is "world", then the expression will be evaluated to "Hello world".
  • 100 + 10l * 2.5: Integers, longs and floating point numbers are similar to their Java counterparts.
  • true / false: Boolean values equivalent to their Java counterparts.
  • null: Represents no specific value, similar to its Java counterpart. none is an alias for null.


Both lists and maps can be created directly within the template.

  • ["apple", "banana", "pear"]: A list of strings
  • {"apple":"red", "banana":"yellow", "pear":"green"}: A map of strings

The collections can contain expressions.


Pebble allows you to calculate values using some basic mathematical operators. The following operators are supported:

  • +: Addition
  • -: Subtraction
  • /: Division
  • %: Modulus
  • *: Multiplication


You can combine multiple expressions with the following operators:

  • and: Returns true if both operands are true
  • or: Returns true if either operand is true
  • not: Negates an expression
  • (...): Groups expressions together


The following comparison operators are supported in any expression: ==, !=, <, >, >=, and <=.

{% if user.age >= 18 %}
{% endif %}


The is operator performs tests. Tests can be used to test an expression for certain qualities. The right operand is the name of the test:

{% if 3 is odd %}
{% endif %}

Tests can be negated by using the is not operator:

{% if name is not null %}
{% endif %}

Conditional (Ternary) Operator

The conditional operator is similar to its Java counterpart:

{{ foo ? "yes" : "no" }}

Null-Coalescing Operator

The null-coalescing operator allows to quickly test if a variable is defined (exists) and to use an alternative value if not:

{% set baz = "baz" %}
{{ foo ?? bar ?? baz }}

{# results in: 'baz' #}

{{ foo ?? bar ?? raise }}
{# results: an exception because none of the 3 vars is defined  #}

Operator Precedence

In order from highest to lowest precedence:

  • .
  • |
  • %, /, *
  • -, +
  • ==, !=, >, <, >=, <=
  • is, is not
  • and
  • or

Parent tasks with Flowable tasks

When using nested Flowable Tasks, only the direct parent task is accessible via taskrun.value. To access a parent task higher up the tree, you can use the parent and the parents expressions.

The following flow shows how to access the parent tasks:

id: each_switch
namespace: dev

  - id: simple
    type: io.kestra.core.tasks.debugs.Return
    format: "{{ }} > {{ taskrun.startDate }}"

  - id: hierarchy_1
    type: io.kestra.core.tasks.flows.EachSequential
    value: ["a", "b"]
      - id: hierarchy_2
        type: io.kestra.core.tasks.flows.Switch
        value: "{{ taskrun.value }}"
            - id: hierarchy_2_a
              type: io.kestra.core.tasks.debugs.Return
              format: "{{ }}"
            - id: hierarchy_2_b
              type: io.kestra.core.tasks.debugs.Return
              format: "{{ }}"

            - id: hierarchy_2_b_second
              type: io.kestra.core.tasks.flows.EachSequential
              value: ["1", "2"]
              - id: switch
                type: io.kestra.core.tasks.flows.Switch
                value: "{{ taskrun.value }}"
                    - id: switch_1
                      type: io.kestra.core.tasks.debugs.Return
                      format: "{{ parents[0].taskrun.value }}"
                    - id: switch_2
                      type: io.kestra.core.tasks.debugs.Return
                      format: "{{ parents[0].taskrun.value}} {{ parents[1].taskrun.value }}"
  - id: simple_again
    type: io.kestra.core.tasks.debugs.Return
    format: "{{ }} > {{ taskrun.startDate }}"

The parent variable gives direct access to the first parent, while the parents[INDEX] gives you access to the parent higher up the tree.

Was this page helpful?