Skip to content

Cluster variables

A cluster variable is a named configuration value that you manage through the API instead of inside a process model. Typical values are service endpoints, thresholds and feature flags that change between environments. Your process models read them in expressions, so you can change a value without redeploying a model.

Cluster variables work the same way in all three modes (Embedded, Bundled and Cluster). There is no console page for them yet; you manage them through the API.

Every cluster variable has a scope:

ScopeWho can read it in expressions
GlobalEvery process instance of every tenant
TenantOnly the process instances of that one tenant

A name is unique within its scope. The same name can exist at global scope and in any number of tenants at the same time, each with its own value.

The value can be any JSON value except null: a string (an empty string is allowed), a number, true or false, a list or an object.

Names are 1 to 256 characters long and use only ASCII letters, digits and the characters _ ~ @ . + -. A name may start with a digit.

Expressions read cluster variables through the reserved FEEL path tinyconductor.vars. It has three parts:

ExpressionGives
tinyconductor.vars.cluster.NAMEthe global value of NAME
tinyconductor.vars.tenant.NAMEthe value of NAME in the instance’s own tenant
tinyconductor.vars.env.NAMEthe tenant value if the tenant has one, otherwise the global value

For example, a service task can take its job type from a cluster variable with =tinyconductor.vars.env.PAYMENT_WORKER, and an input mapping can pass an endpoint to a worker with =tinyconductor.vars.env.PAYMENT_ENDPOINT. Expressions can read cluster variables wherever they read process variables: input and output mappings, job types and retries, gateway conditions, timers, user-task assignments and dates, message correlation keys, and the called process or decision of a call activity or business rule task.

Some details matter when you rely on these values:

  • Nested values. Use the normal path syntax, for example tinyconductor.vars.env.CONFIG.retries.
  • The tenant value replaces the global value as a whole. If the global CONFIG is {"retries": 5, "mode": "a"} and the tenant’s CONFIG is {"mode": "b"}, then tinyconductor.vars.env.CONFIG.retries is null. Objects are never merged.
  • An empty string is a value. A tenant value of "" is used; it does not fall back to the global value.
  • A missing name is null. Reading a name that does not exist gives null. That alone raises no incident, but a place that needs a real value (for example a job type) treats null as it always does.
  • Only the full path finds a value. A plain name such as NAME never reads a cluster variable; plain names are process variables only. The namespace and its parts on their own, such as tinyconductor.vars.env, are an empty object: they never list the variables.
  • Process variables come first. If an instance has a process variable named tinyconductor (set at start, by a job or by a mapping), every tinyconductor.vars… expression in that instance reads that variable instead, and does not fall back to the cluster variables. Do not give a process variable that name.
  • Other tenants stay hidden. An instance never sees another tenant’s values, through any namespace.

A value is read each time an expression is evaluated. A change you make applies to every expression evaluated after the API answers, including in instances that are already running. A value that was already used stays as it was: for example, a timer whose duration came from a cluster variable keeps its due date when you change the variable while the timer waits.

Changing a cluster variable never triggers anything by itself. In particular a conditional event does not re-check its condition when a cluster variable changes; only a change to the instance’s own variables does that.

Reading cluster variables creates no process variables and no variable records. A value appears among an instance’s variables only if a mapping copies it there.

Managing cluster variables through the API

Section titled “Managing cluster variables through the API”
OperationRequestSuccess
Create a global variablePOST /v2/cluster-variables/global with {"name": …, "value": …}200
Create a tenant variablePOST /v2/cluster-variables/tenants/{tenantId} with {"name": …, "value": …}200
Read oneGET /v2/cluster-variables/global/{name} or …/tenants/{tenantId}/{name}200
Replace its valuePUT on the same path with {"value": …}200
Delete itDELETE on the same path204
SearchPOST /v2/cluster-variables/search200

A successful create, read or update answers with the variable:

{"name": "PAYMENT_ENDPOINT", "scope": "GLOBAL", "tenantId": null, "value": "\"https://payments.example.com\""}

value is the whole value as JSON text, so a string keeps its quotes. tenantId is null for a global variable.

The write is complete when the answer arrives: every expression evaluated afterwards reads the new value.

SituationAnswer
The name breaks the name rules, the value is missing or null, or the body has any other property (including metadata or kind)400; nothing is stored
The name already exists in that scope409
The name does not exist in that scope (update, delete or read)404
You lack the permission403
The path names a tenant other than yours403 for a write, 404 for a read

A global variable and a tenant variable never stand in for each other: to update or delete a tenant variable, use its tenant path, even if a global variable has the same name.

The search body is optional and has three parts, all optional:

{
"filter": {"scope": "TENANT", "name": {"$like": "PAYMENT_*"}},
"sort": [{"field": "name", "order": "ASC"}],
"page": {"limit": 50}
}
  • filter takes name, value, tenantId, scope and isTruncated. A plain value matches exactly; an object takes $eq, $neq, $exists, $in, $notIn and $like (* matches any run of characters, ? one character). {"tenantId": {"$exists": false}} selects the global variables.
  • sort takes name, value, tenantId and scope. By default results are sorted by name, global before tenant.
  • page takes limit (1 to 10,000, default 100) and either from (an offset) or a cursor, after or before, taken from the previous answer’s page.endCursor or page.startCursor.

Values of 8,192 characters or more are cut to their first 8,191 characters in search results and marked "isTruncated": true. Add ?truncateValues=false to the URL to get whole values, or read the variable on its own.

The search lists the global variables and your own tenant’s variables, never another tenant’s.

Four permissions govern cluster variables:

PermissionAllows
cluster-variable:readReading and searching
cluster-variable:createCreating
cluster-variable:updateReplacing a value
cluster-variable:deleteDeleting

The built-in admin role has all four. The operator and viewer roles may read. You can also grant them on single variables with the CLUSTER_VARIABLE resource type and the variable’s name (see Identity and access); such a grant covers the name at both scopes.

The search never refuses: it lists only the variables you may read, and it answers an empty list when you may read none.

A global write affects every tenant. In a Cluster deployment that serves several tenants, anyone who may create, update or delete cluster variables can change what every tenant’s processes read. Give those three permissions only to people and clients you trust with the whole deployment.

Tenant variables belong to the tenant of the request. To manage another tenant’s variables, act in that tenant.

Expressions need no permission to read cluster variables.

ModeStorage
EmbeddedIn memory, with the rest of the engine’s state
BundledIn PostgreSQL, so they survive a restart. Tenant variables are protected per tenant like all tenant data
ClusterIn PostgreSQL. Tenant variables are protected per tenant like all tenant data. Global variables are kept apart and can only be changed through the API

Values are read inside the engine’s own work, so very large values make every evaluation that reads them slower. There is no size or count limit beyond the request-size limit of the API.

Each change writes a CLUSTER_VARIABLE record (CREATED, UPDATED or DELETED) with the person or client who made it. A refused change (an existing or missing name) writes a rejection record. A global variable’s record has an empty tenantId; it is kept with the records of the tenant of the person who made the change.