Placeholders

Use dynamic data in your workflows with Liquid templating, standard filters, and Embed Workflow's custom filters and tags.

Overview

Placeholders let you inject dynamic data into your workflows. They use Liquid templating, so you get standard Liquid syntax (variables, conditionals, loops, filters) plus a set of custom filters and tags specific to Embed Workflow.

Variables

Use double curly braces to output a variable's value:

1
{{ name }}

With data { "name": "John" }, this renders John.

Variable names are case-insensitive. These all resolve to the same value:

1
2
3
{{ Name }}
{{ NAME }}
{{ name }}

When a variable holds an object or array, it is serialized to JSON:

1
{{ user }}

With data { "user": { "name": "John", "age": 30 } }, this renders {"name":"John","age":30}.

Nested access

Read nested values with dot notation:

1
2
{{ user.email }}
{{ user.address.city }}

Bracket access works with a numeric index or a quoted key. A bare word inside brackets is treated as another variable, not a literal key, so it won't resolve:

1
2
3
{{ items[0].name }}     # works: numeric index
{{ form["status"] }}    # works: quoted key
{{ form[status] }}      # does NOT resolve: status is read as a variable

Missing values

A placeholder that references a field which doesn't exist resolves to an empty string. Use the default filter to supply a fallback:

1
2
{{ nickname | default: "Unknown" }}
{{ user.nickname | default: user.name }}

Conditionals

Use {% if %}, {% elsif %}, {% else %}, and {% endif %}:

1
2
3
4
5
{% if name != '' %}
  Hello {{ name }}
{% else %}
  Hello stranger
{% endif %}

Chain multiple conditions with {% elsif %}:

1
2
3
4
5
6
7
{% if workspace_id != '' %}
  ?workspace_id={{ workspace_id }}
{% elsif project_id != '' %}
  ?project_id={{ project_id }}
{% elsif title != '' %}
  ?title={{ title }}
{% endif %}

Loops

Use {% for %} to iterate over an array:

1
2
3
{% for item in items %}
  - {{ item.name }}: {{ item.value }}
{% endfor %}

Standard Liquid filters

Filters transform a value. Apply one with a pipe (|) after the variable. All standard Liquid filters are available, including:

FilterDescriptionExample
upcaseConvert to uppercase{{ name | upcase }}
downcaseConvert to lowercase{{ name | downcase }}
capitalizeCapitalize the first letter{{ name | capitalize }}
stripRemove surrounding whitespace{{ name | strip }}
replaceReplace text{{ name | replace: "old", "new" }}
splitSplit into an array{{ csv | split: "," }}
sizeLength of a string or array{{ items | size }}
dateFormat a date string{{ date | date: "%Y-%m-%d" }}
plusAdd a number{{ count | plus: 1 }}
minusSubtract a number{{ count | minus: 1 }}
appendAppend text{{ url | append: "/path" }}
prependPrepend text{{ path | prepend: "https://" }}

Filters can be chained:

1
{{ name | downcase | replace: " ", "-" }}

Custom filters

These filters are specific to Embed Workflow.

html

Converts plain text to HTML paragraphs. The content is HTML-escaped for safety, while any Liquid syntax inside it is preserved. A double newline starts a new paragraph, a single newline becomes a <br>.

1
{{ message | html }}

With data { "message": "Hello world\n\nThis is a new paragraph\nWith a line break" }:

1
<p>Hello world</p><p>This is a new paragraph<br>With a line break</p>

datetime

Converts a Unix timestamp (in seconds) to a formatted date string. It defaults to ISO 8601 (%Y-%m-%dT%H:%M:%SZ). This is different from the standard date filter, which formats an existing date string.

1
2
3
{{ created_at | datetime }}
{{ created_at | datetime: "%B %d, %Y" }}
{{ created_at | datetime: "%m/%d/%Y %H:%M" }}

With data { "created_at": 1705312800 }:

1
2
3
2024-01-15T12:00:00Z
January 15, 2024
01/15/2024 12:00

Common format codes:

CodeDescriptionExample
%Y4-digit year2024
%mMonth (01-12)01
%dDay (01-31)15
%HHour, 24h (00-23)12
%MMinute (00-59)00
%SSecond (00-59)00
%BFull month nameJanuary
%AFull day nameMonday
%sUnix timestamp1705312800

expand

Flattens a nested object or array into readable key-value lines, useful for debugging or displaying structured data as plain text. It takes a prefix string:

1
{{ address | expand: "address" }}

With data { "address": { "street": "123 Main St", "city": "Springfield", "state": "IL" } }:

1
2
3
address[street]: 123 Main St
address[city]: Springfield
address[state]: IL

It handles nested structures and arrays:

1
{{ data | expand: "response" }}

With data { "data": { "users": [{ "name": "Alice" }, { "name": "Bob" }] } }:

1
2
response[users][0][name]: Alice
response[users][1][name]: Bob

pdf

Renders text as a PDF document and returns it URL-safe Base64-encoded, so the binary document can pass through the text-based params pipeline. It's decoded back to bytes when sent as a multipart file, so it's typically used as the file field of a multipart upload action.

1
{{ document_content | pdf }}

With data { "document_content": "Protocol amendment summary..." }, this renders the Base64-encoded bytes of a PDF containing that text.

Custom tags

Tags perform an action and are wrapped in {% %}.

redact

Outputs a variable's value normally, but replaces it with <FILTERED> when the template is rendered in redact mode. Use it to keep sensitive values out of logs, execution history, and audit trails while still sending the real value in the request.

1
Authorization: Bearer {% redact api_token %}
  • Normal mode: Authorization: Bearer sk-1234567890
  • Redact mode: Authorization: Bearer <FILTERED>
When to use redaction

Reach for {% redact %} on anything that shouldn't appear in logs: API keys and tokens, passwords and secrets, PII, and financial data. The real value is substituted at runtime, while any logged or displayed copy shows <FILTERED>.

base64

Base64-encodes a variable's value (URL-safe encoding):

1
{% base64 api_key %}

With data { "api_key": "my-secret-key" }, this renders the URL-safe Base64 encoding of my-secret-key.

email_rfc_base64

Builds an RFC 2822 email message and returns it as a URL-safe Base64-encoded string, for APIs that require raw email format (such as the Gmail API).

1
{% email_rfc_base64 from: sender_email to: recipient_email subject: email_subject body: email_body %}

Each parameter references a variable name in your data:

ParameterDescriptionRequired
fromSender email addressYes
toRecipient email addressYes
subjectSubject lineYes
bodyBody textYes
ccCC recipientsNo
bccBCC recipientsNo

The tag generates the full message with headers (From, To, Subject, Date, Message-ID, Content-Type, MIME-Version) and returns it Base64-encoded.

Putting it together

Dynamic URLs

1
https://api.example.com/v2/workspaces{% if workspace_id != '' %}?workspace_id={{ workspace_id }}{% elsif project_id != '' %}?project_id={{ project_id }}{% endif %}

Conditional header with redaction

1
{% if api_token != '' %}Bearer {% redact api_token %}{% endif %}

Date math

Chain date, plus, and datetime to do arithmetic on a date: convert it to a Unix timestamp, add 86400 seconds (one day), and format it back.

1
{{ created_at | date: "%s" | plus: 86400 | datetime }}

Multi-pass rendering

If a variable's value itself contains Liquid, it's resolved on a later pass. This lets you compose templates from data.

1
{{ email_body }}

With data:

1
2
3
4
{
  "email_body": "Created on: {{ created_at }}",
  "created_at": "2024-01-15"
}

The first pass resolves email_body to Created on: {{ created_at }}, and the next pass resolves created_at to produce Created on: 2024-01-15.

Data sources

Placeholders can read from several sources:

  • Account data: values available across your application, such as configuration and global settings.
  • User data: user-specific values stored in user_data, such as preferences and custom fields.
  • Execution data: values provided when the workflow starts, such as event and form data or the API payload.
  • Action data: values from previously executed actions, such as API responses and computed results.
  • Node data: values configured in the action form by the workflow designer.

Precedence

When the same field name exists in more than one source, the more specific source wins. From lowest to highest precedence:

  1. Account data
  2. User data
  3. Execution data
  4. Action data
  5. Node data

Next steps