---
title: "Using Template Expressions"
description: "Understand how template expressions work across integrations, tools, and steps."
---
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.
## Where template expressions are used
The same template engine is used across superglue:
- **Integrations** – connection strings and URL templates
- **Step configuration** – `urlHost`, `urlPath`, `headers`, `queryParams`, `body`
- **Transforms** – final transforms and data selectors
## Template expression modes
There are two kinds of template expressions: **simple expressions** and **arrow function expressions**.
### Simple expressions (legacy)
Simple expressions resolve values directly from the context (payload, credentials, previous step results) without any JavaScript.
Syntax:
- `<<variable>>`
- `<<variable.key>>` for nested fields
**Examples:**
In the context of a tool step:
```typescript
headers: {
"Authorization": "Bearer <<stripe_apiKey>>"
},
queryParams: {
user_id: "<<userId>>"
}
```
or in an integration:
```text
postgres://<<username>>:<<password>>@<<host>>:<<port>>/<<database>>
```
### Arrow function expressions (recommended)
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.
```js
<<(sourceData) => {
// read from sourceData and return the value to insert
}>>
```
<Note>
Arrow function expressions are also used in places that are pure code (for example, the
`finalTransform` field), where you don’t wrap them in `<< >>`. They still receive the same
`sourceData` context object and behave the same way.
</Note>
**Examples:**
```typescript
headers: {
"Authorization": "<<(sourceData) => `Bearer ${sourceData.credentials.apiKey}`>>"
}
```
```typescript
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 context
Template expressions evaluate against a **context object** that depends on where they're used (step config, integration, 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:
### Integration context
The context is built from the integration credentials (for example, `username`, `password`, `access_token`).
The `sourceData` might look like this:
```json
{
"username": "db_user",
"password": "secret123",
"host": "db.example.com",
"port": "5432",
"database": "production"
}
```
<Note>
Integration templates are only resolved at tool execution time and get merged
with tool-specific credentials.
</Note>
### Tool step context
The context is the **aggregated step input**, which includes:
- `payload` – workflow input payload
- `credentials` – resolved credentials for the integration
- 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:
```json
{
"payload": { "userId": "12345", "startDate": "2024-01-01" },
"credentials": { "apiKey": "sk_test_..." },
"getCustomers": {
"data": [{ "id": "cus_123", "email": "user@example.com" }]
}
}
```
### Pagination variables
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:
```typescript
queryParams: {
"page": "<<page>>",
"per_page": "<<limit>>"
}
```
Offset-based pagination:
```typescript
queryParams: {
"offset": "<<offset>>",
"limit": "<<limit>>"
}
```
Cursor-based pagination:
```typescript
queryParams: {
"cursor": "<<cursor>>",
"limit": "<<pageSize>>"
}
```
### Stop condition
The stop condition is a JavaScript function that determines when to stop fetching pages. It receives two 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 fetched so far
The function should return `true` to **stop** pagination, or `false` to continue.
**Examples:**
Stop when no more pages (using response metadata):
```javascript
(response, pageInfo) => !response.data.has_more;
```
Stop when data array is empty:
```javascript
(response, pageInfo) => response.data.items.length === 0;
```
Stop when cursor is null or missing:
```javascript
(response, pageInfo) => !response.data.next_cursor;
```
Stop after fetching a specific number of items:
```javascript
(response, pageInfo) => pageInfo.totalFetched >= 1000;
```
Stop when on last page (from total pages header):
```javascript
(response, pageInfo) =>
pageInfo.page >= parseInt(response.headers["x-total-pages"] || "1");
```
Combine multiple conditions:
```javascript
(response, pageInfo) => {
const items = response.data.results || [];
return (
items.length === 0 || !response.data.next || pageInfo.totalFetched >= 5000
);
};
```
### Final transform context
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:
```json
{
"payload": { "userId": "12345" },
"getCustomers": {
"data": [{ "id": "cus_123", "email": "user@example.com" }]
},
"createPaymentIntent": {
"data": { "id": "pi_456", "status": "succeeded" }
}
}
```