DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS /// DOCS
ROUTINES·VARIABLES

Variables

Variables are named values that Routines read at runtime. They hold secrets, configuration, and — in Chain Routines — the outputs of previous steps. Most variables are defined at the workspace level; step outputs are transient and scoped to the current run.

Variables let you separate configuration from code. An API key, a project ID, or an email address goes in a variable; your function or instruction references it by name. When the value changes, you update the variable once and every Routine picks up the new value on its next run — no code changes, no redeployment.


Variable types

Secrets — encrypted at rest, decrypted only at runtime inside a sandbox. Use secrets for API keys, webhook URLs, passwords, and any value you don't want visible in logs or UI. Secret values are masked in the Onnie UI after creation: you can see the key name, the description, and when it was last updated, but the value is not readable after saving. You can overwrite a secret (rotation) but you cannot read the current value back through the UI.

Config variables — plain text, visible in the workspace variables panel to any editor. Use config values for non-sensitive settings: project IDs, table IDs, email addresses, thresholds, feature flags. Config variables are readable by any workspace editor and are appropriate for values that are stable and non-sensitive.

Step outputs — transient values produced by a step in a Chain Routine. When a Function step returns an object or an Agent step produces a response, those values become variables that later steps in the same run can reference. Step output variables are automatically created at runtime and are scoped to the current run. They are not persisted after the run completes.

All persistent variables (secrets and config) live in the workspace_variables table, scoped to a workspace with a unique key per workspace. They are available to any Routine in the workspace regardless of which project the Routine belongs to.


Defining a variable

Go to SettingsVariablesNew variable.

Fill in:

  • Key — the name you'll use to reference the variable. Convention is SCREAMING_SNAKE_CASE for secrets (e.g. SLACK_WEBHOOK_URL, ENRICHMENT_API_KEY) and camelCase or snake_case for config values. Keys must be unique within the workspace.
  • Value — the value to store. For secrets, this is stored encrypted and will not be shown again after saving.
  • Secret — toggle on if the value should be encrypted and masked. Leave off for plain config values.
  • Description — optional, but recommended. Document what the variable is used by, where its value comes from, and who owns it. Makes rotation less painful six months later.

Variables can also be created from inside the Function editor: reference a variable name that doesn't exist yet and the editor will prompt you to create it inline.

Workspace scope. All persistent variables are workspace-scoped. There is no per-routine or per-project variable scope for persistent values. If you need a value that differs per project, pass it in the Routine's input_template as a plain JSON value rather than storing it as a variable.


Referencing variables

The syntax for referencing a variable depends on where you're using it.

In a Routine's input template (JSON):

Use double-brace interpolation. Onnie resolves the variable before passing the input to the function. Secret variables are decrypted and injected at this point; the function receives the plaintext value.

{
  "webhookUrl": "{{SLACK_WEBHOOK_URL}}",
  "projectId": "{{DEFAULT_PROJECT_ID}}",
  "threshold": "{{SCORE_THRESHOLD}}"
}

Inside an Agent Routine's instruction or a Chain Agent step's prompt:

Same double-brace syntax. Variables in prompts are resolved before the agent sees them.

Summarize all open Tasks in project {{DEFAULT_PROJECT_ID}}.
Post the result to the channel at {{STANDUP_WEBHOOK_URL}}.

Inside a Function via the SDK:

Use sdk.variables.get(key) to read a variable at runtime. This is the preferred approach for sensitive values — the secret is not interpolated into a JSON config where it might appear in logs.

export default async function handler(input, sdk) {
  const apiKey = await sdk.variables.get('ENRICHMENT_API_KEY')
  const response = await sdk.fetch('https://api.enrichment.example/lookup', {
    headers: { Authorization: `Bearer ${apiKey}` },
    // ...
  })
}

If the variable key doesn't exist in the workspace, sdk.variables.get() throws a variable_not_found error and the run fails. Check that variable keys in your code match the keys defined in Settings → Variables.


Step outputs in Chains

In a Chain Routine, each step's return value becomes a set of variables available to subsequent steps. The reference syntax is {{stepId.field}} where stepId is the step's configured id and field is a key in the step's return value.

If a step with id enrich returns:

{ "score": 0.87, "category": "enterprise", "summary": "Large fintech company, Series B." }

Subsequent steps can reference:

  • {{enrich.score}}0.87
  • {{enrich.category}}"enterprise"
  • {{enrich.summary}} → the summary string

Agent step output is a single text string, not a structured object. If an Agent step with id classify produces a response, it's accessible as {{classify.output}}. If you need structured data from an Agent step, instruct the agent to output JSON and parse it in the next Function step.

Step output variables are resolved in graph order. A step can only reference outputs from steps that have already completed. Referencing an output from a step that hasn't run yet produces an empty string, not an error — so be careful about step ordering when constructing inputs.

Step output variables do not persist beyond the run. If you need a value to survive and be available to a future run, write it to a workspace record, table, or use sdk.variables.set(key, value) to update a workspace variable with the computed result.

For more detail on Chain step types and how outputs wire together, see Chain Routines.


Secret rotation

To update a secret:

  1. Go to SettingsVariables.
  2. Find the variable row and click Edit.
  3. Enter the new value. The old value is overwritten immediately; there is no version history or rollback.
  4. Save.

The next time any Routine runs and reads that variable, it receives the new value. There is nothing to redeploy or restart — Routines read variables fresh at the start of each run. A run that is already mid-execution when you rotate a secret will finish with the value it read at run start.

For services that issue short-lived tokens, you can automate rotation with a Function that refreshes the token and writes it back. Note that sdk.variables.set always writes the value as a non-secret (plain text) variable — use this pattern for short-lived access tokens that are frequently replaced, not for long-lived credentials that must remain encrypted:

export default async function handler(input, sdk) {
  const response = await sdk.fetch('https://auth.example/token', {
    method: 'POST',
    body: JSON.stringify({ client_id: input.clientId, client_secret: input.clientSecret }),
  })
  const { access_token, expires_in } = await response.json()
  await sdk.variables.set('SERVICE_ACCESS_TOKEN', access_token)
  return { rotated: true, expires_in }
}

Schedule this Function to run before the token expires. Other Routines that read SERVICE_ACCESS_TOKEN will always get a valid token without any manual intervention.

//TIP

If you delete a variable that a Routine references, the next run will fail with variable_not_found. Before deleting a variable, search your workspace functions and Routine instructions for its key name to find all callers.