Variable
The configuration knob you attach to other elements: a typed key-value setting — URL, count, flag, or JSON blob — that cascades nearest-wins down the element → app → circle → platform chain and injects into execution as both `{{ vars.slug }}` and an environment variable.
Working with it
Opening a Variable launches a code editor — its dedicated working surface.
How it appears
The same element type rendered as a definition, a circle instance, and a live workspace card.
When to use / not
When to use
- Pulling a URL, threshold, count, or feature flag out of code so it can be changed without editing the element that reads it.
- Giving one setting a different value per environment — `development`, `staging`, `production` — from a single attached variable via overrides.
- Sharing one config value across many elements, referenced consistently as `{{ vars.slug }}` rather than duplicated inline.
- Holding an encrypted secret (API key, token) via `set_value` with sensitive=true, so the value lives vault-encrypted and never as plaintext in the spec.
When not to use
- A reusable text/instruction template for an LLM — use prompt; variable is for configuration values, not authored prose.
- Intercepting or transforming requests at runtime — variable has no middleware phase; it resolves once at cascade time. Reach for a request-phase modifier like rate-limit or filter-words.
- A plain non-sensitive value you want mirrored to a public GitHub repo — variables are excluded from public-mirror pushes because they commonly carry secrets.
Topology
Attaches to another element as a modifier, shaping that element's behaviour rather than running on its own.
Properties
typestring- Data type of the variable value
value- The variable value (type depends on type field). Accepts any JSON-compatible value.
value_typestring- Data type of the value (alias for 'type' — either field is accepted)
default- Default value if not set
validationobject- Value validation rules
requiredboolean- Whether a value must be provided
sensitiveboolean- Whether to mask value in logs/UI
scopestring- Variable scope
overrideobject- Environment-specific overrides
dataobject- Initial configuration data (key-value pairs). Set at creation time; use set_value operation for runtime updates.
Capabilities
Defined for this element
- Storage
- Crypto
- Observe
Operations
- attachPOST
- deleteDELETE
- delete_valuePOST
- detachPOST
- getGET
- get_attached_modifiersGET
- get_valueGET
- list_attachmentsGET
- list_keysGET
- set_valuePOST
- updatePATCH
Ports
Inputs
- valueconfig
- typeconfig
- environment_overridesconfig
- secretconfig
Composition
Errors / when it fails
- Required variable must have a value or default
- Fails unless:
value is not None or default is not None - validation.pattern only applies to string value_type
- Fails unless:
value_type == 'string' - validation.min/max only apply to number value_type
- Fails unless:
value_type == 'number'
Validation rules
- Sensitive variable with global scope - consider environment-specific values
- Required variable without default - deployment will fail if not set
Variable (variable)
Category: modifiers | Form: | Symbol: Va
Define typed configuration values with environment overrides
Defines typed key-value configuration that is injectable into execution environments. Cascade strategy: nearest (NearestMap) — local variable values override inherited ones with the same name. Stored in resolved.variables as HashMap<String, Value>. Resolution order: element → app → circle → platform (most specific wins). Variables are environment_injectable — they appear as environment variables in actor execution contexts. No middleware phase — variables are resolved at cascade time, not per-request. Use variable for configuration values (API keys, thresholds, URLs); use prompt for text templates. Inherits standard ops (get, update, delete, attach, detach) — no custom operations.
Guide
Overview
A Variable stores plain-text configuration values that can be referenced by other resources. Variables are safe to commit to version control and are visible in the UI.
Why Variables Exist
- Configuration: Store URLs, feature toggles, counts, settings
- Environment-Specific: Different values per environment (dev, staging, prod)
- Reusable: Reference from multiple resources using
{{ vars.slug }} - Version Controlled: Changes tracked in Git
Directory Structure
api-base-url/
├── README.md # Documentation
├── .triform/
│ ├── triform.yaml # kind: control/variable
│ ├── contract.yaml # Capabilities
│ ├── spec.yaml # Value definition
│ └── schema.json # Validation
└── examples/
└── with-overrides.yaml
Creating a Variable
$ triform create control/variable api-base-url
Configuration
Basic Variable
kind: control/variable
slug: api-base-url
spec:
type: string
value: "https://api.example.com"
description: "Base URL for API requests"
Variable Types
# String
spec:
type: string
value: "https://api.example.com"
# Number
spec:
type: number
value: 100
# Boolean
spec:
type: boolean
value: true
# Object (JSON)
spec:
type: object
value:
key: "value"
nested:
setting: true
# Array
spec:
type: array
value:
- "item1"
- "item2"
Environment Overrides
Different values for different environments:
kind: control/variable
slug: api-base-url
spec:
type: string
value: "https://api.example.com" # Default (production)
overrides:
development:
value: "http://localhost:3000"
staging:
value: "https://staging.api.example.com"
Inheritance
Variables can inherit from parent scopes:
kind: control/variable
slug: api-base-url
spec:
type: string
inherit_from: "@org/api-base-url" # Use org-level default
# Override only in specific cases
overrides:
development:
value: "http://localhost:3000"
Referencing Variables
In Resource Specs
kind: actor/action
slug: fetch-data
spec:
runtime:
env:
API_URL: "{{ vars.api-base-url }}"
MAX_RETRIES: "{{ vars.max-retries }}"
In Code (SDK)
from triform import variables
# Get variable value
api_url = variables.get("api-base-url")
# Get with default
max_retries = variables.get("max-retries", default=3)
# Get typed
config = variables.get_object("app-config")
import { variables } from '@triform/sdk';
const apiUrl = await variables.get('api-base-url');
const maxRetries = await variables.get('max-retries', { default: 3 });
Validation
Variables can have validation rules:
spec:
type: string
value: "https://api.example.com"
validation:
# String patterns
pattern: "^https://.*"
# String length
min_length: 10
max_length: 200
# Allowed values
enum:
- "https://api.example.com"
- "https://staging.api.example.com"
spec:
type: number
value: 100
validation:
min: 1
max: 1000
SDK Usage
Python
from triform import variables
# Get single variable
api_url = variables.get("api-base-url")
# Get all variables for current environment
all_vars = variables.list()
# Check if variable exists
if variables.exists("feature-enabled"):
# ...
# Get with metadata
var = variables.get_with_metadata("api-base-url")
print(var.value)
print(var.updated_at)
print(var.scope)
TypeScript
import { variables } from '@triform/sdk';
const apiUrl = await variables.get('api-base-url');
const allVars = await variables.list();
const varMeta = await variables.getWithMetadata('api-base-url');
console.log(varMeta.value, varMeta.updatedAt);
Variable vs Secret
| Aspect | Variable | Secret |
|---|---|---|
| Storage | Plain text in Git | Encrypted in Vault |
| Visibility | Visible in UI/logs | Never shown |
| Rotation | Manual only | Automatic support |
| Audit | Standard logging | Full access logging |
| Use for | URLs, counts, flags | API keys, passwords |
Rule of thumb: If you’d be worried about the value appearing in a log file, use a Secret.
Runtime Behavior
| Property | Value |
|---|---|
| Cascade | nearest — the closest attached variable map wins per key |
| Eval Order | n/a (no middleware phase; resolved at element configuration load time) |
| Phase | n/a |
| Fail Action | n/a (missing variable references surface as configuration errors at startup) |
| Applies To | actors, frontend, data, external |
Variables are environment-injectable: resolved values are made available as {{ vars.<slug> }} template substitutions and as environment variables during execution. They do not participate in the request middleware pipeline.
Files in This Resource
README.md- Documentation.triform/triform.yaml- Metadata.triform/contract.yaml- Capabilities.triform/spec.yaml- Value definition
Relationships
- Attaches to: circle, app, automation, python, javascript, ruby, rust-fn, go-fn, csharp, hitl, triformer, claude-code, codex, open-code, external-agent, lab, diagram, sql, document, vector, graph, timeseries, contacts, organizations, entity, schema, files, cookie-jar, view, spa, ssr, three-d, slack, teams, discord, email, matrix, mattermost, rocketchat, http, queue, websocket, schedule, platform-trigger, phone-number, linkedin, twitter, facebook, instagram, github, fortnox, platform, chromeless, user-browser, recorder, board, planning-board, sales-board, recruitment-board
Capabilities
- typed: Typed configuration
- env-override: Environment-specific overrides
- secrets: Secret value encryption
- injection: Injected as environment variable
Properties
| Property | Type | Default | Description |
|---|---|---|---|
type | string | "string" | Data type of the variable value |
value | string | — | The variable value (type depends on type field). Accepts any JSON-compatible value. |
value_type | string | "string" | Data type of the value (alias for ‘type’ — either field is accepted) |
default | string | — | Default value if not set |
validation | object | — | Value validation rules |
required | boolean | false | Whether a value must be provided |
sensitive | boolean | false | Whether to mask value in logs/UI |
scope | string | "global" | Variable scope |
override | object | — | Environment-specific overrides |
data | object | — | Initial configuration data (key-value pairs). Set at creation time; use set_value operation for runtime updates. |
values | object | — | Key-value store for multiple named values (written by set_value operation) |
vault_stored | boolean | false | Internal flag: whether value is encrypted in vault |
Operations
attach
Post /ops/attach | Auth: Read
Attach this modifier to a target element
Attaches this modifier to a target element. The target_id must be a UUID of an existing element that supports this modifier type (check applies_to in definition.yaml). Priority controls evaluation order when multiple modifiers of the same type are attached — lower priority runs first. The attachment is stored in element_modifiers table. Cascade resolution runs at bond-time to merge this modifier into the target’s resolved config. Common mistake: attaching to an incompatible element type — check topology rules first.
delete
Delete /ops/delete | Auth: Admin
Delete element (soft delete)
Soft delete — sets state to ‘deleted’ but retains the record. Cannot delete elements that have children (has_no_bond precondition) or active runs. Requires admin auth and confirmation.
delete_value
Post /ops/delete_value | Auth: Write
Delete a variable value by key, environment, or base value
Removes a stored value. If key is provided, removes spec.values[key]. If environment is provided, removes spec.override[environment]. If neither, removes the base spec.value.
detach
Post /ops/detach | Auth: Read
Detach this modifier from a target element
Removes this modifier from a target element. Requires the target_id. Pervasive modifiers (audit, policy) can only be detached at the level they were originally attached — inherited pervasive modifiers cannot be detached by child elements. After detach, cascade resolution re-runs to remove this modifier’s effect from the resolved config.
get
Get /ops/get | Auth: Read
Get element details
Element is already resolved by the routing layer — this returns the cached element, not a fresh DB query. Use the path /api/{circle}/{slug} to address elements.
get_attached_modifiers
Get /ops/attached/{target_id} | Auth: Read
Get all modifiers attached to a target element
Lists all modifiers attached to a specific target element, including modifier_id, type, subcategory, and priority. Useful for debugging cascade resolution or understanding which policies apply to an element before invoking it.
get_value
Get /ops/get_value | Auth: Read
Get the variable’s current value (decrypts if sensitive)
Returns the current value. For sensitive variables, decrypts from the vault. Lookup cascade when an environment is specified: (1) spec.override[environment] for exact match, (2) spec.values[key] or spec.value as fallback. When the requested environment has no override, the response ALWAYS includes env_requested, env_found=false, fell_back_to_base=true, and available_environments so callers can detect the silent fallback. Environment names are arbitrary strings set by whoever called set_value — they are NOT validated against an enum. Use list_keys to discover which environments exist on a given variable.
list_attachments
Get /ops/targets | Auth: Read
List all elements this modifier is attached to
Returns all target elements where this modifier is currently applied. Shows target_id, target_type, priority, and cascade_policy.
list_keys
Get /ops/list_keys | Auth: Read
List all stored keys, environments, and whether a base value exists
Returns the list of named keys in spec.values, environment names in spec.override, and whether a base value (spec.value or vault-stored) exists.
set_value
Post /ops/set_value | Auth: Write
Set the variable’s value (vault-encrypted if sensitive)
Sets the variable’s value. When sensitive=true or value_type=secret, the value is encrypted via VaultService and stored in the secret_values table — the spec stores only a “[encrypted]” marker, never the plaintext. Non-sensitive variables store the value directly in spec.value. Use this operation instead of update for sensitive values.
update
Patch /ops/update | Auth: Write
Update element
Partial update — send only the fields you want to change.
spec,name, andintentionare all independently optional.specMUST be a JSON object when present; deep-merged into the existing spec by default. Empty{"spec":{}}preserves existing spec content but still records a new version (no-op for content, not for version state). To clear/replace the entire spec wholesale send{"spec":{...},"deep":false}. List-typed spec fields use replace semantics (the patch list replaces the existing list, no array merging). Coordinates Git + DB writes. Slug cannot be changed after creation.
Error Codes
| Code | Class | Retryable | Description |
|---|---|---|---|
VARIABLE_NOT_FOUND | not_found | no | Variable does not exist |
VARIABLE_TYPE_MISMATCH | validation | no | Value doesn’t match declared type |
Lifecycle / runtime
Defined for this element
Execution model: sync
Observability
Defined for this element
Metrics
- evaluation_count
- rejection_count
Events
- variable.evaluated
- variable.rejected
Pricing / cost
Platform default
Operation costs
- create: free
- update: free
- delete: free
- get: free
- list: free
- invoke: 10000 micro-AU
- tool_use: free
Set it up
- Valuestring
- The variable's value
- Typestring
- Type (alias)string
- Alias for 'type'
- Sensitivestring
- Hide value in logs and UI