Skip to content

Using Template Expressions

Most of the time you don’t have to write template expressions yourself—the agent generates them for you. Still, understanding how they work helps when you’re debugging or tweaking a tool.

At their core, template expressions use the <<expression>> pattern, where expression is evaluated at runtime.

The same template engine is used across superglue:

  • Systems – connection strings and URL templates
  • Step configurationurlHost, urlPath, headers, queryParams, body
  • Transforms – final transforms and data selectors

There are two kinds of template expressions: simple expressions and arrow function expressions.

Simple expressions resolve values directly from the context (payload, credentials, previous step results) without any JavaScript.

Syntax:

  • <<variable>>

Examples:

In the context of a tool step:

headers: {
"Authorization": "Bearer <<stripe_apiKey>>"
},
queryParams: {
user_id: "<<userId>>"
}

or in a system:

postgres://<<username>>:<<password>>@<<host>>:<<port>>/<<database>>

Arrow function expressions use JavaScript arrow functions that receive the context as a single parameter (typically named sourceData) and return the value to insert. superglue runs these functions in a sandboxed JavaScript VM.

<<(sourceData) => {
// read from sourceData and return the value to insert
}>>

Examples:

headers: {
"Authorization": "<<(sourceData) => `Bearer ${sourceData.credentials.apiKey}`>>"
}
body: {
posts: "<<(sourceData) => {
const fromJsonPlaceholder =
(sourceData.fetchJsonPlaceholderPosts ?? []).map(p => ({
id: p.id,
title: p.title,
source: 'jsonplaceholder'
}));
const fromDummyJson =
(sourceData.fetchDummyJsonPosts?.posts ?? []).map(p => ({
id: p.id,
title: p.title,
source: 'dummyjson'
}));
return [...fromJsonPlaceholder, ...fromDummyJson];
}>>"
}

Template expressions evaluate against a context object that depends on where they’re used (step config, system, or transform).

  • Arrow function expressions – context is passed as the function parameter (typically sourceData)
  • Simple expressions – the string inside << >> is resolved against the same context object

The context looks different depending on where the expression is used:

The context is built from the system credentials (for example, username, password, access_token).

The sourceData might look like this:

{
"username": "db_user",
"password": "secret123",
"host": "db.example.com",
"port": "5432",
"database": "production"
}

The context is the aggregated step input, which includes:

  • payload – workflow input payload
  • credentials – resolved credentials for the system
  • Previous step results keyed by step ID (for example, getCustomers)
  • currentItem – current iteration data when the step runs in a loop

The sourceData object might look like this:

{
"payload": { "userId": "12345", "startDate": "2024-01-01" },
"credentials": { "apiKey": "sk_test_..." },
"getCustomers": {
"data": [{ "id": "cus_123", "email": "user@example.com" }]
}
}

When pagination is configured on a step, the following variables are available in the request configuration (urlPath, queryParams, headers, body):

Variable Description
page Current page number (starts at 1)
offset Current offset (starts at 0, increments by pageSize)
cursor Cursor value extracted from the previous response
limit Same as pageSize
pageSize The configured page size

Examples:

Page-based pagination:

queryParams: {
"page": "<<page>>",
"per_page": "<<limit>>"
}

Offset-based pagination:

queryParams: {
"offset": "<<offset>>",
"limit": "<<limit>>"
}

Cursor-based pagination:

queryParams: {
"cursor": "<<cursor>>",
"limit": "<<pageSize>>"
}

The stop condition is a JavaScript function that determines when to stop fetching pages. It receives three arguments:

  1. response – object containing:

    • data – the parsed response body
    • headers – response headers
  2. pageInfo – object containing:

    • page – current page number
    • offset – current offset
    • cursor – current cursor value
    • totalFetched – total number of items in the merged paginated result, including the current page
    • mergedResult – accumulated paginated result including the current page
  3. sourceData – current step input data, with payload fields at the root plus previous step results

The function should return true to stop pagination, or false to continue.

Examples:

Stop when no more pages (using response metadata):

(response, pageInfo) => !response.data.has_more;

Stop when data array is empty:

(response, pageInfo) => response.data.items.length === 0;

Stop when cursor is null or missing:

(response, pageInfo) => !response.data.next_cursor;

Stop after fetching a specific number of items:

(response, pageInfo) => pageInfo.totalFetched >= 1000;

Stop after fetching the number of items requested by the tool input:

(response, pageInfo, sourceData) => pageInfo.totalFetched >= sourceData.maxResults;

Stop when on last page (from total pages header):

(response, pageInfo) => pageInfo.page >= parseInt(response.headers["x-total-pages"] || "1");

Combine multiple conditions:

(response, pageInfo) => {
const items = response.data.results || [];
return items.length === 0 || !response.data.next || pageInfo.totalFetched >= 5000;
};

The context is a combined view of the entire workflow execution:

  • All step results keyed by step ID
  • The original payload

The sourceData object might look like this:

{
"payload": { "userId": "12345" },
"getCustomers": {
"data": [{ "id": "cus_123", "email": "user@example.com" }]
},
"createPaymentIntent": {
"data": { "id": "pi_456", "status": "succeeded" }
}
}