JSONata Expressions Reference
SignStack uses JSONata, a powerful query and transformation language for JSON data. This reference covers the syntax, operators, and functions you'll use when building dynamic templates and workflows.
Where Expressions Are Used
| Location | Property | Purpose |
|---|---|---|
| Template Fields | valueExpression |
Calculate field display values |
| Template Fields | displayCondition |
Control field visibility (returns boolean) |
| Blueprint Steps | inclusionCondition |
Control step inclusion (returns boolean) |
| Blueprint Steps | onCompleteMappers[].mapperExpression |
Transform entity data after step completion |
| Blueprint Steps | onCompleteValidationExpression |
Validate data before mappers run |
| Blueprint Documents | inclusionCondition |
Control document inclusion |
| Blueprint Documents | iterator |
Generate multiple document instances |
| Blueprint Participants | isRequired |
Dynamic participant requirement |
Expression Context
All expressions have access to:
{
"entities": {
"client_info": { "name": "Jane", "email": "jane@example.com", "tier": "Premium" },
"deal_info": { "value": 50000, "status": "pending" }
},
// In onCompleteMappers only:
"fields": {
"documentKey": {
"fieldKey": { "value": { "text": "..." } }
}
}
}
Basic Syntax
Accessing Data
// Direct property access
entities.client_info.name; // "Jane"
// Nested properties
entities.client_info.address.city; // Access nested object
// Array access
entities.items[0]; // First item
entities.items[-1]; // Last item
entities.items[0].name; // Property of first item
Operators
// Comparison
entities.deal_info.value > 50000 // Greater than
entities.deal_info.value >= 50000 // Greater than or equal
entities.deal_info.value = 50000 // Equal (note: single =)
entities.deal_info.value != 50000 // Not equal
entities.deal_info.value < 50000 // Less than
// Logical
condition1 and condition2 // Both must be true
condition1 or condition2 // Either can be true
not(condition) // Negation
// Arithmetic
entities.deal_info.quantity * entities.deal_info.unitPrice
entities.deal_info.total - entities.deal_info.discount
entities.deal_info.value / 100
Ternary (Conditional)
// condition ? value_if_true : value_if_false
entities.client_info.tier = 'Premium' ? 0.1 : 0.05;
// Nested ternary
entities.deal_info.value > 100000 ? 'Large' : entities.deal_info.value > 50000 ? 'Medium' : 'Small';
String Concatenation
// Use & for string concatenation
entities.client_info.firstName & ' ' & entities.client_info.lastName;
// Results in: "Jane Doe"
Built-in Functions
String Functions
// Case conversion
$uppercase(entities.client_info.name); // "JANE"
$lowercase(entities.client_info.email); // "jane@example.com"
// Trimming
$trim(' hello '); // "hello"
// Substring
$substring(entities.client_info.name, 0, 1); // "J" (first character)
// Length
$length(entities.client_info.name); // 4
// Contains
$contains(entities.client_info.email, '@'); // true
// Replace
$replace(entities.phone, '-', ''); // Remove dashes
// Split
$split('a,b,c', ','); // ["a", "b", "c"]
// Join
$join(['a', 'b', 'c'], ', '); // "a, b, c"
// Pad
$pad(entities.invoiceNumber, 6, '0'); // "000123"
Number Functions
// Formatting (currency, decimals)
$formatNumber(entities.deal_info.value, '$#,##0.00'); // "$50,000.00"
$formatNumber(entities.rate, '0.00%'); // "5.25%"
// Rounding
$round(3.456, 2); // 3.46
$floor(3.9); // 3
$ceil(3.1); // 4
// Math
$abs(-5); // 5
$sqrt(16); // 4
$power(2, 3); // 8
// Aggregation
$sum(entities.items.price); // Sum of all prices
$average(entities.items.quantity); // Average quantity
$min(entities.items.price); // Minimum price
$max(entities.items.price); // Maximum price
$count(entities.items); // Number of items
Date Functions
// Current timestamp
$now(); // ISO timestamp string
// Format dates
$fromMillis($toMillis(entities.signedDate), '[M01]/[D01]/[Y0001]');
// Common patterns:
// [Y0001]-[M01]-[D01] → 2024-01-15
// [M01]/[D01]/[Y0001] → 01/15/2024
// [MNn] [D1], [Y0001] → January 15, 2024
Array Functions
// Filter
entities.items[status = "active"] // Items where status is active
entities.items[price > 100] // Items with price > 100
// Map/Transform
entities.items.name // Array of all names
entities.items.(name & " - " & $string(price)) // Transform each item
// Sort
$sort(entities.items, function($a, $b) { $a.price > $b.price })
// Reverse
$reverse(entities.items)
// Distinct
$distinct(entities.items.category) // Unique categories
// Append
$append(entities.items, newItem) // Add item to array
// Reduce
$reduce(entities.items.price, function($acc, $val) { $acc + $val }, 0)
Object Functions
// Merge objects (crucial for onCompleteMappers)
$merge([entities.client_info, { "status": "verified" }])
// Get keys
$keys(entities.client_info) // ["name", "email", "tier"]
// Get values
$values(entities.client_info) // ["Jane", "jane@example.com", "Premium"]
// Lookup
$lookup(entities.client_info, "name") // "Jane"
// Spread/pick specific keys
entities.client_info.{ "name": name, "contact": email }
Type Functions
// Check existence
$exists(entities.client_info.middleName); // false if undefined
// Type checking
$type(entities.deal_info.value); // "number"
$type(entities.client_info.name); // "string"
$type(entities.items); // "array"
// Conversion
$string(entities.deal_info.value); // "50000"
$number('123.45'); // 123.45
$boolean(entities.client_info.isActive); // true/false
Conditional Functions
// Assert (throws if condition false)
$assert(entities.deal_info.value > 0, 'Value must be positive');
// Error (throws custom error)
$error('Invalid configuration');
Common Patterns
Safe Property Access
Handle potentially missing data:
// Using $exists
$exists(entities.client_info.middleName) ? entities.client_info.middleName : '';
// Using default with ternary
entities.client_info.tier ? entities.client_info.tier : 'Standard';
Conditional Display
For displayCondition (must return boolean):
// Show only for premium clients with large deals
entities.client_info.tier = "Premium" and entities.deal_info.value > 50000
// Show only if field exists
$exists(entities.deal_info.specialTerms)
// Show based on array content
$count(entities.items) > 0
Entity Updates (onCompleteMappers)
Always use $merge to update entities:
// Add/update properties
$merge([
entities.client_info,
{
name: fields.formDoc.nameField.value.text,
email: fields.formDoc.emailField.value.text,
updatedAt: $now(),
},
]);
// Conditional update
$merge([
entities.deal_info,
{
status: fields.approvalDoc.approved.value.checked ? 'approved' : 'rejected',
},
]);
Formatting Values
// Currency
$formatNumber(entities.deal_info.value, '$#,##0.00');
// Percentage
$formatNumber(entities.rate / 100, '0.00%');
// Phone number
$substring(entities.phone, 0, 3) & '-' & $substring(entities.phone, 3, 3) & '-' & $substring(entities.phone, 6);
// Full name
$trim(entities.firstName & ' ' & ($exists(entities.middleName) ? entities.middleName & ' ' : '') & entities.lastName);
Array Transformations
// Sum line items
$sum(entities.lineItems.(quantity * unitPrice))
// Filter and count
$count(entities.items[status = "pending"])
// Get names as comma-separated list
$join(entities.participants.name, ", ")
// Check if any item matches condition
$count(entities.items[priority = "high"]) > 0
Debugging Tips
-
Start simple: Build expressions incrementally, testing each part.
-
Use Scenarios: Test expressions with different data sets using SignStack Scenarios.
-
Check for typos: Property names are case-sensitive (
client_infovsClientInfo). -
Handle nulls: Use
$exists()to check before accessing nested properties. -
AI assistance: Describe your logic in plain language and ask an LLM to generate the JSONata.
Further Resources
- Official JSONata Documentation
- JSONata Exerciser - Test expressions online
- Making Fields Dynamic - Template expressions guide
- Updating Entities - onCompleteMappers guide