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:
| Filter | Description | Example |
|---|---|---|
upcase | Convert to uppercase | {{ name | upcase }} |
downcase | Convert to lowercase | {{ name | downcase }} |
capitalize | Capitalize the first letter | {{ name | capitalize }} |
strip | Remove surrounding whitespace | {{ name | strip }} |
replace | Replace text | {{ name | replace: "old", "new" }} |
split | Split into an array | {{ csv | split: "," }} |
size | Length of a string or array | {{ items | size }} |
date | Format a date string | {{ date | date: "%Y-%m-%d" }} |
plus | Add a number | {{ count | plus: 1 }} |
minus | Subtract a number | {{ count | minus: 1 }} |
append | Append text | {{ url | append: "/path" }} |
prepend | Prepend 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 32024-01-15T12:00:00Z January 15, 2024 01/15/2024 12:00
Common format codes:
| Code | Description | Example |
|---|---|---|
%Y | 4-digit year | 2024 |
%m | Month (01-12) | 01 |
%d | Day (01-31) | 15 |
%H | Hour, 24h (00-23) | 12 |
%M | Minute (00-59) | 00 |
%S | Second (00-59) | 00 |
%B | Full month name | January |
%A | Full day name | Monday |
%s | Unix timestamp | 1705312800 |
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 3address[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 2response[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.
1Authorization: Bearer {% redact api_token %}
- Normal mode:
Authorization: Bearer sk-1234567890 - Redact mode:
Authorization: Bearer <FILTERED>
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:
| Parameter | Description | Required |
|---|---|---|
from | Sender email address | Yes |
to | Recipient email address | Yes |
subject | Subject line | Yes |
body | Body text | Yes |
cc | CC recipients | No |
bcc | BCC recipients | No |
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
1https://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:
- Account data
- User data
- Execution data
- Action data
- Node data
Next steps
- Authoring a Configuration: where placeholders show up in a config file
- Quick Start: build your first workflow
