Pebble Syntax in Kestra Expressions

Use this page when you need help writing expressions rather than looking up a specific filter or function.

Pebble basics

Pebble templates use two primary delimiters:

  • {{ ... }} to output the result of an expression
  • {% ... %} to control template flow with tags such as if, for, or set

Examples:

{{ flow.id }}
{% if inputs.region == "eu" %}Europe{% endif %}

To escape Pebble syntax literally, use the raw tag described in Operators, Tags, and Tests.

Accessing values

Use dot notation for standard property access:

{{ foo.bar }}

Use bracket notation for special characters or indexed access:

{{ foo['foo-bar'] }}
{{ items[0] }}

Parsing nested expressions

Kestra renders expressions once by default. If a variable contains Pebble that should be evaluated later, use render():

variables:
trigger_or_yesterday: "{{ trigger.date ?? (execution.startDate | dateAdd(-1, 'DAYS')) }}"
input_or_yesterday: "{{ inputs.mydate ?? (execution.startDate | dateAdd(-1, 'DAYS')) }}"
tasks:
- id: yesterday
type: io.kestra.plugin.core.log.Log
message: "{{ render(vars.trigger_or_yesterday) }}"
- id: input_or_yesterday
type: io.kestra.plugin.core.log.Log
message: "{{ render(vars.input_or_yesterday) }}"

This pattern is especially useful with namespace variables, composed flow variables, and fallback logic based on trigger context.

Multiline JSON bodies

When an HTTP request body contains multiline user input, avoid partial string interpolation. Instead, build the whole payload as a single Pebble expression so JSON escaping happens correctly.

id: multiline_input_passed_to_json_body
namespace: company.team
inputs:
- id: title
type: STRING
defaults: This is my title
- id: message
type: STRING
defaults: |-
This is my long
multiline message.
- id: priority
type: INT
defaults: 5
tasks:
- id: hello
type: io.kestra.plugin.core.http.Request
uri: https://kestra.io/api/mock
method: POST
body: |
{{ {
"title": inputs.title,
"message": inputs.message,
"priority": inputs.priority
} | toJson }}

Common syntax patterns

Comments

Use Pebble comments with {# ... #}:

{# This is a comment #}
{{ "Visible content" }}

In YAML, continue to use # for comments outside the expression itself.

Literals and collections

Pebble supports:

  • strings: "Hello World"
  • numbers such as 100 + 10l * 2.5
  • booleans: true, false
  • null: null
  • lists: ["apple", "banana"]
  • maps: {"apple":"red", "banana":"yellow"}

Named arguments

Filters, functions, and macros can accept named arguments:

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

Macros

Macros are reusable template snippets:

{% macro input(type="text", name, value="") %}
type: "{{ type }}", name: "{{ name }}", value: "{{ value }}"
{% endmacro %}
{{ input(name="country") }}

Macros only access their local arguments.

Control flow and fallbacks

Common patterns:

  • if and elseif for branching
  • for for iteration
  • ?? for fallback values
  • ? : for ternary expressions

Examples:

{{ inputs.mydate ?? (execution.startDate | dateAdd(-1, 'DAYS')) }}
{% for article in articles %}
{{ article.title }}
{% else %}
No articles available.
{% endfor %}
{% if category == "news" %}
{{ news }}
{% elseif category == "sports" %}
{{ sports }}
{% else %}
Select a category
{% endif %}

For full operator and tag details, see Operators, Tags, and Tests.

When to leave this page

Was this page helpful?