Variables
Gateway variables are a shared key/value store for a project's pipelines, scripts and screens. They hold the state that does not belong to a tag and does not deserve a database table: a counter, a last-run timestamp, a feature flag, a cached token.
They are the server-side half of the Variables feature. The other half, client variables, lives in one browser tab and never reaches the server. → Context API
Scope New in 1.0.8
| Scope | Visible to | Deleted |
|---|---|---|
| Project — the default | That project's pipelines, Gateway and client scripts, and views | With the project |
| Global | Every project | Only when deleted |
A variable belongs to the project that set it unless its name has been declared global. Two
projects can each keep a shift_target of their own without colliding. A global name is global
everywhere: no project keeps a private copy shadowing it.
To change a variable's scope, use the scope button on its row in the pipeline Variables drawer
or the Designer's Gateway variables window. Make global shares it with every project;
Make project-scoped asks first, then moves the value into this project, and every other project
stops seeing it. A script declares a name global with system.vars.set(name, value, scope="global").
Making a name global is refused while another project still holds its own variable of that name, and the refusal names those projects: promoting it would silently throw their values away. Delete or rename it there first.
Project variables are included when the project is backed up or exported, and purged when it is deleted.
Variables are keyed by project from 1.0.8. Every persistent variable that existed before the upgrade was moved to global, so every pipeline, script and screen that read it keeps working. Variables created afterwards belong to the project that sets them — so a pipeline in one project no longer sees a new variable another project sets, unless that name is made global. Review the Scope column after upgrading and make project-scoped whatever only one project uses.
Storage modes
| Mode | Survives restart | Use for |
|---|---|---|
| Persistent | Yes | Counters, last-processed markers, configuration flags |
| Volatile | No | Caches, in-flight state, anything cheap to recompute |
The workspace has a default mode; individual variables can override it at write time.
| Action | Permission |
|---|---|
| List variables and read the default mode | authenticated |
| Change the default storage mode | Configure Shared Variables |
| Delete a variable, or change its scope 1.0.8 | Edit Pipeline |
Seeing them
Variables are created and changed by scripts and by the Designer's Set variable action; the interface shows them and lets you delete them or change their scope.
| Where | Shows |
|---|---|
| The pipeline workspace's Variables drawer | This project's variables and the global ones: Name, Type, Value, Scope, Where (Persistent or Memory), and the storage default |
| The Designer's project browser, Variables → Gateway | The same set |
| The Designer's project browser, Variables → Client | This project's client variables |
Changed in 1.0.8 The pipeline drawer opens at 1100 px — never so wide that less than 120 px of canvas shows — and its left edge drags to resize it, down to 480 px; double-click the edge to reset it. A long value stays on one line, cut off, with the full value as a tooltip; an object or array value expands into a tree, in the drawer and in both Designer windows.
Deleting a variable asks first, naming the variable and what stops working: a project variable's readers in this project, a global variable's readers in every project, or a client variable's bindings. 1.0.8
Using variables
last = system.vars.get("last_export_ts", 0)
rows = system.db.runQuery(
"SELECT * FROM events WHERE ts > ?", [last], "ReportingDB"
)
if rows:
system.vars.set("last_export_ts", rows[-1]["ts"], "persistent")
count = system.vars.incr("exports_today") # atomic
left = system.vars.decr("labels_left", 10) # atomic subtract
system.vars.set("plant_mode", "RUN", scope="global") # shared by every project
const last = await system.vars.get("last_export_ts", 0);
await system.vars.set("last_export_ts", Date.now(), "persistent");
const count = await system.vars.incr("exports_today");
await system.vars.set("plant_mode", "RUN", undefined, "global");
API
| Call | Returns |
|---|---|
get(name, defaultValue?) | The value, or defaultValue when unset |
set(name, value, storage?, scope?) | true. storage omitted ⇒ workspace default; scope omitted ⇒ the name keeps its scope |
delete(name) | true |
keys() | This project's variable names and the global ones |
incr(name, by?) | The new value after atomically adding by (default 1) |
decr(name, by?) 1.0.8 | The new value after atomically subtracting by (default 1) |
list() | This project's variables and the global ones, with value, type, storage, scope and last-updated time |
Names are letters, digits and -, _, =, /, ., and may not start or end with a dot.
→ system.vars
Use incr for counters
# ❌ race: two pipelines can read the same value and both write n+1
n = system.vars.get("count", 0)
system.vars.set("count", n + 1)
# ✅ atomic
n = system.vars.incr("count")
Variables are shared across concurrently executing pipelines. Read-modify-write loses updates;
incr and decr do not.
Patterns
Incremental extract
last = system.vars.get("etl_watermark", "1970-01-01T00:00:00Z")
rows = system.db.runNamedQuery("changes_since", {"since": last})
# ... process ...
if rows:
system.vars.set("etl_watermark", rows[-1]["updated_at"], "persistent")
Restart-safe: the watermark is persistent, so a restart resumes rather than reprocessing.
Run-once-per-period guard
import datetime
today = datetime.date.today().isoformat()
if system.vars.get("daily_report_date") == today:
return None # already ran
# ... produce the report ...
system.vars.set("daily_report_date", today, "persistent")
Cached credential
token = system.vars.get("api_token")
if not token:
token = fetch_token()
system.vars.set("api_token", token, "volatile") # not written to disk
Variables, tags or a database?
| Need | Use |
|---|---|
| A process value with quality, history, alarms | A tag (internal tag if QUBIQ owns it) |
| Coordination state between pipelines | A variable |
| A setting or a result a screen shows, with no history needed | A variable, bound as vars.<name> |
| A value an operator writes that must be alarmed or kept in history | An internal tag |
| Anything queried, reported on or retained long-term | A database |
A screen can show a gateway variable: bind a property to vars.<name> — read-only, refreshed every
few seconds — and write one with the Set variable action, scope Gateway. In a LiveView,
operators and anonymous visitors can read only the variables that project's views and client
scripts name. 1.0.8 Before 1.0.8 a vars.<name> binding
stayed empty for anyone not signed in to the Studio.
→ Set variable
Cautions
- Not encrypted. Do not store credentials you would not want an operator with script access to read. Use a connection's credential storage instead.
- Not audited per write. Variable changes are not in the security audit journal.
- One name space per project. Within a project, prefix by concern (
etl_watermark,report_lastrun) so unrelated pipelines do not collide — and remember that a global name is shared with every project. - Volatile means volatile. A restart clears it, including a supervisor-initiated service restart.