$context Reference
Most expressions in a workflow work on the output of the previous step. $context is everything else: the data the workflow was started with, the output of any earlier step, who ran the workflow, and which environment it runs in.
$context is available everywhere in a Transform expression, and all of it works in {{ }} placeholders too. For example, {{ $context.user.email }} in an Email step, or {{{ $context.data.trigger.body.orderId }}} in a REST API step's Endpoint URL.
In a Code step, the same information is read with getContext. See Using the same data in a Code step.
Overview
$context
├── env
│ ├── apiDomain domain of the Vince Platform API, e.g. "api.vince.live"
│ └── apiUrl full API address, e.g. "https://api.vince.live"
├── tenant
│ └── tenantId your company's ID in Vince Platform
├── data
│ ├── trigger what started the workflow: header, body, executionParams, executionKeys
│ └── all the output of every step so far, by step ID
├── user the user who ran the workflow
├── workflowExecution
│ └── executionId the ID of this run
└── runtime
├── environment the environment selected for this run, e.g. "dev" or "prd" (or null)
├── connections the environment's connections, by alias
└── variables the environment's variables, by name
data: the trigger input and earlier steps
| Path | Contains |
|---|---|
$context.data.trigger.body | The data the workflow was started with |
$context.data.trigger.header | The headers sent when the workflow was started, for example by an API call |
$context.data.trigger.executionParams | Parameters passed in when the run was started |
$context.data.trigger.executionKeys | The run's execution keys |
$context.data.all.<step ID> | The output of that step, for example $context.data.all.rest_api_1.body |
$context.data.trigger stays the same for the whole run, so any step can read the original input, however many steps came before it.
Refer to earlier steps by ID
$ (in a Transform) and body (in a placeholder) mean the previous step's output. In a short workflow that's easy to follow. In a longer one, it gets hard to tell which step really comes before, and adding or moving a step quietly changes what $ refers to.
We recommend referring to steps by ID every time, even for the previous step. A reference such as $context.data.all.rest_api_1.body:
- says exactly which step it reads from, so the expression can be read on its own,
- keeps working when other steps are added, moved or removed, and
- gives the same result when you test it in the Transform editor, where
$holds the whole loaded run instead of the previous step.
(
$orders := $context.data.all.rest_api_1.body.orders;
$prices := $context.data.all.rest_api_2.body.prices;
…
)
How step IDs are formed
Step IDs aren't shown in the workflow editor, but they follow a fixed pattern: the type of step, an underscore, and a number. The first step of a type gets 1, the next one of the same type 2, and so on:
| Step | IDs |
|---|---|
| Code | code_1, code_2, … |
| Converter | converter_1 |
| Data Lake | data_lake_1 |
email_1 | |
| Excel | excel_1 |
| File Loader | file_loader_1 |
| Filter | filter_1 |
| Generic Filter | generic_filter_1 |
| M3 API | m3_api_1 |
| REST API | rest_api_1 |
| SMS | sms_1 |
| Table Updater | table_updater_1 |
| Transform | transform_1 |
The number is given when the step is added and never changes. Moving a step doesn't renumber it, and deleting rest_api_1 doesn't turn rest_api_2 into rest_api_1. So in a workflow that has been edited a few times, the numbers can be out of order or have gaps. If you're unsure which IDs a workflow's steps have, Load a run in the Transform editor and use the expression $keys($context.data.all). Output then lists the ID of every step in that run.
user: who ran the workflow
When the workflow is triggered by a user, $context.user holds that user's full profile, as set up on the Users page. The most useful fields:
| Path | Contains |
|---|---|
$context.user.userId | The user's ID |
$context.user.email | Email address |
$context.user.firstName, $context.user.lastName | Name |
$context.user.phone | Phone number |
$context.user.group | Access level: TenantAdmin or TenantUser |
$context.user.roles | The user's roles, as a list |
$context.user.tags.<key>[0] | A tag on the user, such as $context.user.tags.warehouse[0]. See Tags below. |
$context.user.labels | The user's labels, as a list |
When the workflow isn't triggered by a user, $context.user holds only userId. Every other field is missing. Expressions that depend on the user should handle that, for example $exists($context.user.email) ? $context.user.email : "integration@yourcompany.com".
Tags
$context.user.tags is always an object, with one property per tag key. The value of each tag is usually a one-item list, not plain text:
"tags": { "warehouse": ["100"], "region": ["NO"] }
So add [0] when you read a tag: $context.user.tags.warehouse[0] returns "100". It's safe either way, because JSONata treats a single value as a one-item list, so [0] also works when a value is stored as plain text or a number. If the user has no such tag, it simply returns nothing.
Leaving out [0] matters most in comparisons. $context.user.tags.warehouse = "100" compares a list with text, which is false even for a user in warehouse 100. No error is shown; the condition just never matches:
/* ✅ true for a user tagged warehouse = 100 */
$context.user.tags.warehouse[0] = "100"
/* ❌ always false when the value is stored as ["100"] */
$context.user.tags.warehouse = "100"
In a {{ }} placeholder it's the other way round: leave out the index. {{ $context.user.tags.warehouse }} prints 100, because a one-item list is printed as its value. A tag with several values is printed with commas, such as OSLO,BERGEN. Don't add an index in a placeholder:
{{ $context.user.tags.warehouse.0 }}isn't valid placeholder syntax. Like any mistake in a template, it leaves the whole text unfilled, including every other placeholder in it, with no error message. See When something doesn't work.{{ $context.user.tags.warehouse.[0] }}works for a list, but prints only the first letter (OforOSLO) if the value is stored as plain text, and nothing if it's a number.
runtime: environment-specific connections and variables
If the workflow is set up for several environments (for example test and production), runtime holds the values for the environment selected when the run starts:
| Path | Contains |
|---|---|
$context.runtime.environment | The name of the selected environment, such as "dev" or "prd", or null if none is selected |
$context.runtime.connections.<alias> | The ID of the connection this environment uses for that alias |
$context.runtime.variables.<name> | The value of that variable in this environment |
With connections chosen by alias, one workflow can use the test system's login in test and the production login in production, with no changes to the workflow itself.
connections and variables are always objects, empty if nothing is defined, and environment is null rather than missing. So $context.runtime.variables.baseUrl is safe to read even when no environment is selected: it simply returns nothing.
tenant, workflowExecution and env
| Path | Contains |
|---|---|
$context.tenant.tenantId | Your company's ID in Vince Platform |
$context.workflowExecution.executionId | The ID of this run. Useful as a reference in messages and in requests to other systems, so a run can be traced back. |
$context.env.apiUrl | The address of the Vince Platform API, such as https://api.vince.live |
$context.env.apiDomain | The same address without https://, such as api.vince.live |
There is no $context.workflow: the workflow's own settings aren't available in expressions.
Using the same data in a Code step
$context is written in expressions and {{ }} placeholders. A Code step is JavaScript, so it doesn't use $context. It reads the same information with the getContext helper instead:
const getContext = require('getContext');
const apiUrl = await getContext('env', 'apiUrl'); // same as $context.env.apiUrl
getContext always returns a Promise, so always await it. In a Code step, getContext is the counterpart of $context:
| In an expression | In a Code step |
|---|---|
$context.env.apiUrl | await getContext('env', 'apiUrl') |
$context.env.apiDomain | await getContext('env', 'apiDomain') |
$context.env | await getContext('env') |
$context.tenant.tenantId | (await getContext('tenant')).tenantId |
$context.user | await getContext('user') |
$context.workflowExecution.executionId | (await getContext('workflowExecution')).executionId |
$context.data.trigger | The trigger argument of your exported function |
$context.data.all | The allSteps argument of your exported function. It is a list in execution order, not an object keyed by step ID |
As in an expression, getContext('user') returns only userId when the workflow isn't started by a user.
Looking up variables and connections dynamically
A Code step can do something that expressions and the other steps can't: it can look up a Variable or a Connection by name while it runs, with getContext('globals.variable', name) and getContext('globals.connection', name). The name can even be built from the data the step is working on. The Transform and the other steps have no way to do this.
// A Variable: the value is in .value, always as a string
const pageSize = await getContext('globals.variable', 'M3_PAGE_SIZE');
const limit = Number(pageSize?.value ?? 100);
// A Connection: pass it to authorizedAxios to make authenticated requests
const connection = await getContext('globals.connection', 'Vince API');
const axios = await authorizedAxios(connection);
Both calls return undefined if nothing with that name exists, so check the result before you use it.
Connections, and secure Variables, come back with their credentials in plain text. Never return them from the step. Anything a step returns is stored as its output.
These tenant-level Variables and Connections aren't the same as $context.runtime.variables and $context.runtime.connections, which hold the values of the environment selected for the run. See Variables.
See getContext: read platform data for everything getContext can return.
Examples
/* Build a link to the Vince Platform API */
$context.env.apiUrl & "/some/path"
/* Use an environment-specific variable and connection */
{
"endpoint": $context.runtime.variables.baseUrl,
"connectionId": $context.runtime.connections.erp
}
/* Read the original trigger input, not the previous step's output */
$context.data.trigger.body.customerId
/* Read the output of a specific earlier step */
$context.data.all.rest_api_1.body
/* Stamp the output with who ran it, and which run produced it */
{
"createdBy": $context.user.userId,
"execution": $context.workflowExecution.executionId
}
Testing expressions that use $context
The JSONata exerciser has no $context. To test an expression there, declare a stand-in at the top of the block, then remove it before you save the Transform:
(
$context := {
"data": { "all": { "rest_api_1": { "body": { "orders": [ { "id": "1000123" } ] } } } },
"user": { "userId": "USER-test", "tags": { "warehouse": ["100"] } }
};
$context.data.all.rest_api_1.body.orders.id
)
Copy real values for the stand-in from a test run in Execution Logs.
Further reading
- Transform component: where most
$contextexpressions are written - Code component:
getContext, the Code step's way of reading the same data - JSONata Masterclass: structure Transform expressions that read from several steps
- Templating Reference:
{{ }}placeholders in URLs, bodies and emails - Error Handling Deep Dive: execution keys and custom status
- Users: user profiles and tags