Custom Functions

A Custom Function is a reusable, versioned snippet of JSONata logic — a composable primitive for business logic. Define it once and call it from any expression in your templates or blueprints — valueExpression, displayCondition, data.transform, or onComplete mappers.

Functions let you encapsulate complex business logic (currency formatting, tax calculations, date math) in a single place. When the logic changes, update the function — not every expression that uses it. Like all SignStack primitives, functions are versioned and authored wherever you prefer — Studio, the CLI as YAML in your repo, or the API.

YAML Format

apiVersion: signstack/v1beta2
kind: JsonataFunction

metadata:
  key: format_currency        # Required, snake_case
  version: 1.0.0              # Required, semver
  name: Format Currency       # Required
  description: Formats a number as USD currency string
  labels:
    category: formatting

spec:
  params:                                          # Optional: function parameters
    - name: amount                                 # Valid JS identifier
      type: number                                # string, number, boolean, or object
      # schema: address_schema@1.0.0              # Required when type is object

  returnType:                                      # Required
    type: string
    # schema: address_schema@1.0.0               # Required when type is object

  body: "'
& $formatNumber(amount, '#,##0.00')" # Required: the JSONata expression

Spec Fields

Field Required Description
params No Input parameters. Each has name, type, and optional schema (for object types).
returnType Yes What the function returns. Has type and optional schema.
body Yes JSONata expression implementing the function. Can call any JSONata built-in ($formatNumber, $round, $now, ...) or another declared SignStack function (see functions below). Max 10,000 chars. Use YAML | for multi-line.
functions No References to other custom functions called via #x3C;alias>() in the body.

Parameter Types

Type Description
string Text values
number Numeric values
boolean True/false
array Array of values
object Complex object (optionally validated against a schema)

Examples

Simple String Function

apiVersion: signstack/v1beta2
kind: JsonataFunction

metadata:
  key: get_full_name
  version: 1.0.0
  name: Get Full Name

spec:
  params:
    - name: firstName
      type: string
    - name: lastName
      type: string

  returnType:
    type: string

  body: "firstName & ' ' & lastName"

Currency Formatter

apiVersion: signstack/v1beta2
kind: JsonataFunction

metadata:
  key: format_currency
  version: 1.0.0
  name: Format Currency
  description: Formats a number as USD currency string

spec:
  params:
    - name: amount
      type: number

  returnType:
    type: string

  body: "'
& $formatNumber(amount, '#,##0.00')"

Math with Rounding

apiVersion: signstack/v1beta2
kind: JsonataFunction

metadata:
  key: calculate_tax
  version: 1.0.0
  name: Calculate Tax

spec:
  params:
    - name: amount
      type: number
    - name: taxRate
      type: number

  returnType:
    type: number

  body: '$round(amount * taxRate, 2)'

Object Parameter with Schema

apiVersion: signstack/v1beta2
kind: JsonataFunction

metadata:
  key: format_address
  version: 1.0.0
  name: Format Address
  description: Formats an address object as a single line string

spec:
  params:
    - name: address
      type: object
      schema: address_schema@1.0.0

  returnType:
    type: string

  body: |
    address.street & ', ' &
    address.city & ', ' &
    address.state & ' ' &
    address.zipCode

Multi-Line with Block Expression

apiVersion: signstack/v1beta2
kind: JsonataFunction

metadata:
  key: calculate_probation_end
  version: 1.0.0
  name: Calculate Probation End Date

spec:
  params:
    - name: startDate
      type: string
    - name: durationMonths
      type: number

  returnType:
    type: string

  body: |
    (
      $start := $toMillis(startDate);
      $msPerMonth := 30 * 24 * 60 * 60 * 1000;
      $endMs := $start + (durationMonths * $msPerMonth);
      $fromMillis($endMs, '[Y]-[M01]-[D01]')
    )

Composing Custom Functions

A function can call another SignStack function once it's declared in the functions block:

apiVersion: signstack/v1beta2
kind: JsonataFunction

metadata:
  key: format_total_compensation
  version: 1.0.0
  name: Format Total Compensation

spec:
  params:
    - name: base
      type: number
    - name: bonus
      type: number

  returnType:
    type: string

  functions:
    money: format_currency@1.0.0   # Local alias → versioned function reference

  body: '$money(base + bonus)'

$money(...) resolves to format_currency@1.0.0 at runtime — same call syntax as JSONata built-ins, just declared first.

Function Calling Another Function

apiVersion: signstack/v1beta2
kind: JsonataFunction

metadata:
  key: create_audit_entry
  version: 1.0.0
  name: Create Audit Entry

spec:
  params:
    - name: action
      type: string
    - name: userId
      type: string

  returnType:
    type: object
    schema: audit_entry@1.0.0

  functions:
    get_full_name: get_full_name@1.0.0

  body: |
    {
      "action": action,
      "userId": userId,
      "timestamp": $now(),
      "id": $string($random())
    }

Using Functions in Expressions

Register functions in a template or blueprint's functions map, then call them via #x3C;alias>():

# In a template or blueprint
spec:
  functions:
    format_currency: format_currency@1.0.0
    get_full_name: get_full_name@1.0.0

Then in any JSONata expression:

# In a template field value
value:
  expr: '$format_currency($.position.salary)'

# In a data transform
data:
  transform: |
    {
      "fullName": $get_full_name($.employee.firstName, $.employee.lastName),
      "salary": $format_currency($.position.salary)
    }

# In a blueprint onComplete mapper
onComplete:
  updates:
    - key: employee
      transform: |
        {
          "probationEnd": $calculate_probation_end($.position.startDate, 3)
        }

Versioning

Functions use semantic versioning. A function moves from Draft to a concrete published version (1.0.0); to make further changes, you cut a new version (1.1.0, 2.0.0). Published versions are immutable — every template or blueprint pinning format_currency@1.0.0 keeps invoking the exact logic it was designed against, even after 2.0.0 ships.

Draft → 1.0.0 (published, immutable)
          ↓ edit + publish
        1.1.0 (published, immutable)
          ↓ edit + publish
        2.0.0 (published, immutable)

Pick the version bump based on the change:

  • MAJOR (2.0.0) — Breaking changes (signature change, return type change, behavioral semantics flipped)
  • MINOR (1.1.0) — Backwards-compatible additions (new optional behavior)
  • PATCH (1.0.1) — Bug fixes (rounding correction, off-by-one)

Best Practices

  1. One concern per function — Keep functions focused. format_currency does one thing well.
  2. Use descriptive parameter namesamount and taxRate are clearer than a and r.
  3. Document with descriptions — Helps your team and AI tools generate correct usage.