Skip to content

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: field or $.field.
  • JSONata in the subject. The subject is text with placeholders. rechnung.nummer stays there literally; correct is Rechnung $(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_list and sql_raw belong 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. $$context is 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 null gives empty text. Numbers and booleans become text, objects and lists become JSON, / in text values becomes -. The path parts __proto__, constructor and prototype always give empty text.
  • Order: first the innermost $fn(…), then $(…) and the legacy forms $nextCounter(…), $ulid(), $dateToPath(…), $toGermanDate(…), finally $fn once more. Brackets and commas inside text arguments belong in '…'.
  • Arguments of $fn containing $ arrive as text ("0815" stays "0815"); literals without $ are converted (5 becomes 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 become undefined, / 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 path antrag_db. The user task "Urlaubsantrag genehmigen" has the display text Urlaub $(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 is Urlaub 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.