Which language where?
Pantarey evaluates expressions in many places: in the Process Designer, in the Data Designer, in forms, dashboards and in data analytics. Behind them are five mechanisms that differ in syntax and behaviour. Most errors arise when a field is written in one language but expects another. This page is the map: which field expects which language, what $ or the context means there, and what a typical expression looks like. Each language has its own page: JSONata in Pantarey, Template functions $fn, Handlebars helpers.
The five mechanisms
JSONata. The whole field is one expression that computes a result from the message or the record: a field, a new object, a filtered list. An empty field leaves the data unchanged; an expression without a result is an error. Fields are written without brackets: antrag_db.mitarbeiter.
Placeholders $(path) with $fn(…). In text fields such as business label, subject or file name, each placeholder is replaced by the value of its path and the rest of the text stays as it is. Functions such as $fn('str_upper', $(name)) reshape the value; a missing value gives empty text.
JavaScript. Gateway conditions, computed fields and validation rules with language JavaScript, and form conditions are evaluated as JavaScript. At the gateway the $(…) placeholders are replaced first, then the comparison runs; in the Data Designer $, obj and root stand for the record.
Handlebars. HTML and text templates for PDF, email and external tasks use {{field}} blocks with one central set of helpers. The context is the result of the associated data mapping, or the whole input of the activity when there is no mapping.
SQL. Queries in the database element, the live data of the external task, the form field Load values, data analytics and SQL widgets are SQL (Athena/Trino). Placeholders $(…) and $fn('sql_list', …) exist only in the database element and in live data; form and analytics use system placeholders such as @currentUserEmail instead.
Two tokens cut across all mechanisms: {{value:name}} inserts a value and is replaced before anything else, but only in designer texts, never in form or record data. {{secret:NAME}} inserts a secret and works exclusively in the inputs of service tasks at paths released for it, such as headers.Authorization in the HTTP request.
Recognition rule
The label tells whether a field expects JSONata or placeholders: fields with the suffix (JSONata) take a whole expression, all other text fields take text with $(…) placeholders.
| Recognition | Language | Reading a field | Example |
|---|---|---|---|
| The whole field is an expression | JSONata | field, $.field, $$.field |
antrag_db.mitarbeiter |
| Placeholders inside text | $(path) + $fn |
$(field), $($$context.field) |
Urlaub $(antrag_db.von) prüfen |
| Comparison at a gateway | JavaScript after $(…) |
$(field) |
$(pruefung.ok) === false |
{{…}} in a template |
Handlebars | {{field}} |
{{currency_eur betrag}} |
select … from … |
SQL | column names, in the database '$(field)' |
select id from invoices where kunde = '$(kunde)' |
Place in the app
The table names the fields by their label in the interface. "Message" is the JSON that travels through the process, see Data in the process; "input" is the message after the activity's input expression.
Process Designer: tasks, Data & Variables
| Place in the app | Language | What $ or the context is |
Example |
|---|---|---|---|
| Configure data & variables → Input expression (JSONata) | JSONata | $ = current message, $$ = root of the message; $fn(…) available, {{value:…}} inserted beforehand |
{ "to": antrag_db.mitarbeiter, "von": $fn('date_toGermanDate', antrag_db.von) } |
| Configure data & variables → Output expression (JSONata) | JSONata | $ = raw result of the activity |
payload.body.pdf |
| Configure data & variables → Result path | path (lowercase, underscore, dots) + $(…) |
placeholders are resolved against the result of the step, not against the message; __proto__, constructor, prototype are forbidden |
ergebnis_$(schritt) |
| Properties → Technical → Business label | $(path) + $fn(…) |
current message when the element is reached; in loops the current element per iteration | RE-$(rechnung_db.prio_seq) $fn('str_upper', $(kunde.name)) |
| User task → Display text | $(path) + $fn(…) |
as business label | Rechnung $(rechnung_db.prio_seq) prüfen |
| User task → Assignment ("Who should perform the task?") | $(path) + $fn(…) |
as business label | $($$context.executedBy) |
| User task → Custom form, Form (extended) | $(…) replacement first, then JSONata |
the form JSON is a JSONata expression over the message; the result must be the field list | [ { "template": request_summary.html }, { "key": "begruendung", "type": "input" } ] |
| User task → time limit Date from process data | $(path) |
ISO date or seconds from the message | $(deadline) |
| Intermediate timer event → Wait time (seconds) | $(path) |
the result is read as a number | $(wartezeit) |
| Throw signal → Payload, signal name | $(path) inside JSON text |
against the message; the result must remain valid JSON | { "orderId": "$(orderId)" } |
Start events
| Place in the app | Language | What $ or the context is |
Example |
|---|---|---|---|
| Start event (schedule, plain start event) → Input expression (JSONata) | JSONata | $ = start message, for the timer only { "$$context": … }. For the user start the input expression is skipped; the form data becomes the message unchanged |
{ "stichtag": $substring($now(), 0, 10) } |
Input and result of the start event are each limited to 50 KB of text; otherwise the validation reports "Start Event Nachricht überschreitet Größenlimit (max. 50 KB)". More on the start types under Start events.
Gateway
| Place in the app | Language | What $ or the context is |
Example |
|---|---|---|---|
| Sequence flow at the XOR gateway → condition expression | JavaScript after $(…) replacement |
text values are automatically wrapped in "…" (unless the placeholder is already quoted), missing values become undefined, null stays null, objects become JSON; {{value:…}} type-aware; no $, no $$, no JSONata |
$(pruefung.ok) === false, $(manager_approval.$$context.selectedAction) === 'approve', $(betrag) > {{value:schwelle}} |
| Gateway → Default flow | – | flows without a condition (except the default) are an error ("Ausgehende Flows ohne Bedingung gefunden"); if no condition matches and there is no default, the step fails ("Kein Default-Pfad definiert …"); the first matching flow in model order wins |
The rules for conditions are under Gateway and conditions.
Database element
| Place in the app | Language | What $ or the context is |
Example |
|---|---|---|---|
| Database element (incoming) → Input, operations Write, Update, Merge | JSONata, then $(…) replacement over the JSON text of the result |
JSONata against the message; $(…) in text values of the object to be written are replaced afterwards |
$sift(vacation_user_entry, function($v, $k) { $k != '$$context' }) |
| Database element Delete → object ID | $(path) |
against the message | $(antrag_db.id) |
| Database element Read → Object id | $(path) |
must be a 26-character ULID after replacement, otherwise "Ungültige ID angegeben" | $(kunde.id) |
| Database element Read → Object prefix | $(path) |
prefix of the document name | 2026/$(monat) |
| Database element Read → Query | SQL with $(path), $fn('sql_list', …), $fn('sql_raw', …) |
text values belong in '…'; outside quotes only numbers, true, false and null are allowed; a value containing ' is reported |
select id from invoices where kunde IN ($fn('sql_list', $(kunden))) |
| Database element Read → Output, Result path | JSONata, path | $ = the object or list read |
$[0] |
Service tasks: PDF, email, grayscale, Handlebars
| Place in the app | Language | What $ or the context is |
Example |
|---|---|---|---|
| Send email to external recipients → Subject | $(path) + $fn(…), single line |
against the input of the activity | Rechnung $(rechnung.nummer) |
| Email → Recipients (To, Cc, Bcc), Sender email, Reply to | JSONata, addresses tolerated as text | JSONata against the input; if the text looks like an address or address list (a@b.de, Name <a@b.de>, comma, semicolon, line break), it is taken literally |
kunde.email, info@firma.de, [antrag_db.mitarbeiter, "chef@firma.de"] |
| Email → Attachments | JSONata | the result is file references { "referenceId": …, "filename": …, "contentType": … }; anything without referenceId is dropped |
invoice.pdfReference, [rechnung_pdf, anlage] |
| Email → HTML data input (JSONata) | JSONata | empty → the whole input is the Handlebars context | { "customer": customer, "items": positions } |
| Email → HTML content | Handlebars | context = result of the HTML data input; {{value:…}} is inserted only here |
{{currency_eur betrag}} |
| Generate PDF from template → Data (JSONata) | JSONata | $ = input of the activity |
$.invoice |
| PDF → PDF template (HTML + Handlebars) | Handlebars | context = result of Data (JSONata), empty → whole input | {{kunde.name}} |
| PDF → File name | $(path) + $fn(…) |
against the whole input, independent of the data mapping; / becomes -; empty = document.pdf; the advanced editor is off here |
Rechnung_$(invoiceNumber).pdf |
| Convert PDF to grayscale → Input file (JSONata) | JSONata | must return a file object with referenceId; default $.document |
attachment, files[0] |
| Create text from template (Handlebars) | Handlebars; input via input expression { content, template } |
content is the context, template the template |
{ "content": $, "template": "Hallo {{name}}" } |
| Email from template (SMTP, Microsoft 365, internal template) | Handlebars | context = contentData; SMTP renders HTML, subject and text, Microsoft 365 only the content (not the subject), the internal template content and subject |
External task
| Place in the app | Language | What $ or the context is |
Example |
|---|---|---|---|
| External task → HTML page, subject, email text, Teams text, pages "Already answered", "Success", "Expired", "Cancelled", success page per action | Handlebars, same helper set as PDF and email | context = data of the input (default input { "data": $ }), otherwise the whole input; plus the markers %%EXTERNAL_URL%%, %%FILE:var%% and <button data-action="…">, which are not Handlebars |
{{name}} |
| External task → notification: Recipients, Cc, Bcc, sender address | $(path) |
data and root of the input mixed, the root wins for equal names |
max@firma.de, $(email) |
| External task → Live data SQL query | SQL with $(…) and $fn('sql_list', …) |
against data; run once when the task is created, result available in the page as window.__liveData |
select * from auftrag where id = '$(auftrag_id)' |
Data Designer
| Place in the app | Language | What $ or the context is |
Example |
|---|---|---|---|
| Attribute → tab Computed → Language JSONata or JS, see Computed fields | JSONata or JavaScript | JSONata: $ = record or list row, $root = whole record, $old = saved previous state (null on creation). JS: $, obj, root, $old |
$sum(positionen.(menge * preis)), $old.status != status ? $now() : $old.status_seit |
| Tab Validation → Language, expression, Error message, see Validation rules | JSONata or JavaScript; error message with $(fieldname) |
JSONata: $ and $root = record, no $old; JS: $, obj, root = record; rule satisfied = truthy |
bis >= von; "Bis $(bis) darf nicht vor Von $(von) liegen." |
| Attribute → tab Conditions (required, hide, disable), see Dynamic forms | JavaScript in the browser | data (whole record), model (parent object, in lists the row), field, formControl, value, loaded (values from Load values) |
model.status === 'abgelehnt' |
| Attribute → Fallback value | $(path) + $fn(…) |
against the record on save, only when the field is empty | $fn('nextId', 'kunde') |
| Document name, Document name builder, see Document name | $(path) + $fn(…) |
against the record; value-list values are replaced by their label beforehand; / inside a value becomes -, / in the template creates folders; the result is cut at 500 characters |
$fn('date_toPath', $(datum))/R-$fn('str_padStart', $(prio_seq), 5, '0') |
Form: Load values
| Place in the app | Language | What $ or the context is |
Example |
|---|---|---|---|
Form field Load values (sql-values), see Forms |
SQL with system placeholders @currentUserEmail, @currentDate, @currentYear |
no $(…) placeholders, no form values; the result sits under loaded.<key> in the form state, not in the record |
SELECT CAST(work_days AS JSON) AS work_days FROM employees WHERE lower(e_mail) = lower('@currentUserEmail') |
Dashboards
| Place in the app | Language | What $ or the context is |
Example |
|---|---|---|---|
| Widget Record view → Configure record view → JSONata expression | JSONata in the browser, without $fn and other Pantarey functions |
against the channel message, root = message; the result is an object or an ID, empty or null keeps the current record; Test expression with Sample message (JSON) |
rechnungsempfaenger |
| Widget Planning board → intent → condition and texts | condition: JSONata in the browser; texts: $(entry.x), $(row.x), $(range.start), $(user.email), $(choice) |
context { entry, row, range, user }, entry and row contain data and meta fields mixed; true offers the action, false or no result hides it, a text shows it disabled with that reason |
entry.status = 'geplant' |
Data analytics
| Place in the app | Language | What $ or the context is |
Example |
|---|---|---|---|
| Data analytics, SQL in widgets | SQL | system placeholders @currentUserEmail, @currentDate; no $(…) placeholders |
The most common mix-ups
$(field)in a JSONata field. In the input or output expression$(…)is a function call and fails. In JSONata the field is written without brackets:fieldor$.field.- JSONata in the subject. The subject is text with placeholders.
rechnung.nummerstays there literally; correct isRechnung $(rechnung.nummer). {{secret:NAME}}in a form or a user task. The token stays as text. Secrets work only in the inputs of service tasks at released paths, see Secrets.$fn('sql_list', …)in JSONata. The message is "fn(): not allowed in jsonata: 'sql_list'".sql_listandsql_rawbelong in SQL queries with$(…); in JSONata a list is built directly./becomes-. With every$(…)placeholder a slash in the value becomes a hyphen, not only in the document name but also in the subject, the business label and the file name. Exception: gateway conditions.- The gateway knows no
$. A condition is JavaScript;$,$$and JSONata functions do not exist there.$$contextis reachable only inside a placeholder:$(aufgabe.$$context.selectedAction) === 'approve'.
Placeholder rules
- A placeholder is
$(path); the path may contain letters, digits,_,.,$,-,@,#and%, which is why$($$context.executedBy)works. - A missing field or
nullgives empty text. Numbers and booleans become text, objects and lists become JSON,/in text values becomes-. The path parts__proto__,constructorandprototypealways give empty text. - Order: first the innermost
$fn(…), then$(…)and the legacy forms$nextCounter(…),$ulid(),$dateToPath(…),$toGermanDate(…), finally$fnonce more. Brackets and commas inside text arguments belong in'…'. - Arguments of
$fncontaining$arrive as text ("0815" stays "0815"); literals without$are converted (5becomes a number,true,false,null;'5'stays text). - Lengths: business label 500, display text 300, assignment 200, result path 1000 characters. If a limit is exceeded, the step fails with
FIELD_LENGTH_EXCEEDED. The document name is cut at 500 characters instead of being rejected. - Different rules apply at the gateway: text values are wrapped in
"…", missing values becomeundefined,/stays/.
Values and secrets
{{value:name}} is replaced in all designer texts, type-aware in code fields (an object value stays an object, a text in JSONata becomes "…"), as text in text fields. The deploy checks for unknown names. In PDF and email HTML templates the value is inserted only at render time. Everything about it under Values in the process.
{{secret:NAME}} is replaced by Pantarey only in service tasks at the paths that the respective service releases, such as URL and authorization header in the HTTP request, credentials for SMTP, Microsoft 365, FTP, SharePoint, Teams or E-POST. Elsewhere the token stays. Everything about it under Secrets.
Example
In the process "Urlaub beantragen" (request leave) of Musterfirma GmbH, "Antrag speichern" writes the record with the input
$sift($, function($v, $k) { $k != '$$context' })(JSONata) and the result pathantrag_db. The user task "Urlaubsantrag genehmigen" has the display textUrlaub $(antrag_db.mitarbeiter) vom $fn('date_toGermanDate', $(antrag_db.von))(placeholders). The gateway after it checks$(entscheidung.$$context.selectedAction) === 'approve'(JavaScript). "Zusage senden" builds the email with the input expression (JSONata), the subject isUrlaub genehmigt: $(antrag_db.von)(placeholders), the HTML content uses{{von}}(Handlebars).
Frequently asked questions
How do I tell whether a field expects JSONata or placeholders?
By the label. If it says (JSONata), the whole field is an expression and a field is addressed without brackets. Otherwise it is a text field in which only $(…) placeholders are replaced.
Why does my expression appear literally in the subject?
The subject takes placeholders, not JSONata. kunde.name stays text; $(kunde.name) is replaced.
Can a placeholder access $$context?
Yes, with $($$context.executedBy) for the last step or $(aufgabe.$$context.selectedAction) when the task's result sits under aufgabe.
Do $fn functions work in dashboards?
No. The record view widget and the planning board evaluate JSONata in the browser, without Pantarey functions. Only the JSONata standard library applies there.
Why does my folder separator change in the subject?
Placeholders always turn / in text values into -. If a folder is wanted in the document name, the / belongs in the template, not in the value.
Next step
JSONata in Pantarey: variables,
$$context, ten basic patterns, error behaviour and testing with the live preview.