Flow Language & JSON Path

Flow steps can dynamically reference, transform, and expand data from previous steps or from the user’s input. This is powered by a mini-language that combines JSON Path queries with special operators. You can use these anywhere a Flow step accepts parameters or in an ObjectTransformer’s result field.

Overview

Each step’s parameters can contain embedded strings like "$.input.description" (plain JSON Path) or advanced operators:

  • @Map(path, indexName) — loop over an array, producing transformed items
  • @Index(indexName) — get the loop index from a @Map
  • @Extend(path) — merge properties from a source object into the current object
  • @Format(template, ...params) — build strings by interpolating values
  • @Replace(target, search, replacement) — replace occurrences in a string

Flow Language processes these recursively before each step runs — letting you feed outputs from prior steps into new ones.

Currently, you can use Flow Language on step parameters and ObjectTransformer result fields. We’re considering enabling it for the entire Flow configuration — let us know if you’d like that!

Basic JSON Path

Referencing data

  • "$.input.someField" — references the top-level Flow input.
  • "$.results.previousStepId.someValue" — references a previous step’s results.
  • "$.store.someKey" — references a value from the Flow’s store.
  • "$$" — references the current array item inside a @Map (or the entire source if not in a map).

Any field that starts with $ is treated as a JSON Path expression and resolved at runtime.

{
  someParameter: '$.input.userName',
}
// If the user typed "Alice", resolves to:
{
  someParameter: 'Alice',
}

You can reference nested elements and array indices:

{
  nested: '$.input.user.profile.name',
  indexed: '$.input.users[5].profile.name',
}

Escaping

If you need literal strings that would otherwise be interpreted as operators or JSON Path, use backslash escaping:

  • \@ — literal @ (prevents operator parsing)
  • \{ — literal { (prevents @Format interpolation)
  • \} — literal }
  • \\ — literal backslash

For example, if you want a string that starts with @ but isn’t an operator:

{
  email: '\\@user.com',
  template: 'Price is \\{not interpolated\\}',
}

Operators

@Format

Constructs a string by interpolating values into {} placeholders.

@Format(template, param1, param2, ...)

Example

Given the input:

{
  pet: {
    color: 'yellow',
    age: 5,
  },
}

And the schema:

{
  description: "@Format('The cat is {} and she\\'s {} years old.', $.pet.color, $.pet.age)",
}

Result:

{
  description: "The cat is yellow and she's 5 years old.",
}
  • {} marks where interpolation happens.
  • It can take as many parameters as you want.
  • If a target isn’t a string, it’ll be stringified.
  • The template itself can be a JSON Path: @Format($.template, $.param1, $.param2).

@Replace

Returns the target string with every occurrence of search replaced by replacement.

@Replace(target, search, replacement)

Example

// Input:
{ user: { fullName: 'John Doe' } }
 
// Schema:
{ newName: "@Replace($.user.fullName, 'Doe', 'Smith')" }
 
// Result:
{ newName: 'John Smith' }

Inside a @Map loop

// Input:
{ words: ['foo_bar', 'baz_bar'] }
 
// Schema:
{ cleaned: ['@Map($.words)', { v: "@Replace($$, '_bar', '')" }] }
 
// Result:
{ cleaned: [{ v: 'foo' }, { v: 'baz' }] }

@Map

Iterates over a list, applying a sub-schema for each element.

@Map(path, optionalIndexName)

Basic example

Given the input:

{
  recipes: [
    { name: 'Sushi rolls', timeToPrepare: 90 },
    { name: 'Kare bowl', timeToPrepare: 60 },
  ],
}

And the schema:

{
  parsedList: [
    '@Map($.recipes, idx)',
    {
      originalObject: '$$',
      preparationTime: '$$.timeToPrepare',
      index: '@Index(idx)',
    },
  ],
}

Result:

{
  parsedList: [
    {
      originalObject: { name: 'Sushi rolls', timeToPrepare: 90 },
      preparationTime: 90,
      index: 0,
    },
    {
      originalObject: { name: 'Kare bowl', timeToPrepare: 60 },
      preparationTime: 60,
      index: 1,
    },
  ],
}
  • $$ is the current item from the array.
  • @Index(idx) returns the current loop index.

Nested maps

You can nest as many Map operators as you need. Each retains its own index counter.

Given the input:

{
  arrays: [{ prompts: ['a', 'b'] }, { prompts: ['c', 'd'] }],
}

And the schema:

['@Map($.arrays)', ['@Map($$.prompts)', { prompt: '$$' }]]

Result:

[{ prompt: 'a' }, { prompt: 'b' }, { prompt: 'c' }, { prompt: 'd' }]

Nested maps keeping array structure

Given the input:

{
  arrays: [
    [1, 2],
    [3, 4],
  ],
}

And the schema:

{
  flatArray: [
    '@Map($.arrays, outerIndex)',
    [
      [
        '@Map($$, innerIndex)',
        {
          value: '$$',
          indexes: {
            outer: '@Index(outerIndex)',
            inner: '@Index(innerIndex)',
          },
        },
      ],
    ],
  ],
}

Result:

{
  flatArray: [
    [
      { value: 1, indexes: { outer: '0', inner: '0' } },
      { value: 2, indexes: { outer: '0', inner: '1' } },
    ],
    [
      { value: 3, indexes: { outer: '1', inner: '0' } },
      { value: 4, indexes: { outer: '1', inner: '1' } },
    ],
  ],
}

Targeting other arrays with the current index

The index counter can be used to access elements in other arrays.

Given the input:

{
  a: [1, 2, 3],
  b: [{ v: 'aValue' }, { v: 'bValue' }, { v: 'cValue' }, { v: 'dValue' }],
}

And the schema:

{
  mapped: [
    '@Map($.a, index)',
    {
      value: '$.b[@Index(index)].v',
    },
  ],
}

Result:

{
  mapped: [{ value: 'aValue' }, { value: 'bValue' }, { value: 'cValue' }],
}

Note how there are only 3 items — the map iterates over a which has 3 elements. If a had 4 items but b only had 3, the fourth would resolve to null.

@Extend

Merges properties from a source object into the current object.

@Extend(path)

Basic example

Given the input:

{ user: { name: 'Alice', age: 30 } }

And the schema:

{
  '@Extend': '$.user',
  role: 'admin',
}

Result:

{ name: 'Alice', age: 30, role: 'admin' }

Inside a @Map

Say you want to iterate over a list and add a new field to each object:

Given the input:

{
  users: [
    { name: 'Alice', age: 30 },
    { name: 'Bob', age: 33 },
  ],
}

And the schema:

{
  users: [
    '@Map($.users, idx)',
    {
      '@Extend': '$$',
      role: 'admin',
      order: '@Index(idx)',
    },
  ],
}

Result:

{
  users: [
    { name: 'Alice', age: 30, role: 'admin', order: 0 },
    { name: 'Bob', age: 33, role: 'admin', order: 1 },
  ],
}

Last updated on March 19, 2026