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

  1. Start simple: Build expressions incrementally, testing each part.

  2. Use Scenarios: Test expressions with different data sets using SignStack Scenarios.

  3. Check for typos: Property names are case-sensitive (client_info vs ClientInfo).

  4. Handle nulls: Use $exists() to check before accessing nested properties.

  5. AI assistance: Describe your logic in plain language and ask an LLM to generate the JSONata.


Further Resources