> ## Documentation Index
> Fetch the complete documentation index at: https://docs.connie.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Variables

> Pass data from the trigger, your list and earlier steps into later steps, read what a step produced, and fix variables that come out empty.

Variables are how the steps in a workflow pass information to each other. They let one email step write a different, personal message to every lead, using the name from your list and the email address an earlier step found.

**Where:** in any step's settings, click the **+** at the end of a field (tooltip **Insert variable**), or type **@** · open a workflow → **Build** tab → click a step

## What is a variable?

If you've ever done a **mail merge**, you already know variables. You write one letter, "Dear \[First name]", and each copy comes out with the right person's name. The "\[First name]" bit is a placeholder that gets filled in later.

A variable in Connie is exactly that: a placeholder for a value that is only known when the workflow runs. You write a step once. Each time the workflow runs, Connie swaps every variable for the real value for that run.

| You write (once) | Row 1: Ana at Acme | Row 2: Ben at Globex |
| - | - | - |
| Hi **First Name**, congrats on the news at **Company**! | Hi Ana, congrats on the news at Acme! | Hi Ben, congrats on the news at Globex! |

In the builder, variables appear as coloured **chips** (pills) inside a field, labelled with where the value comes from, for example "Find Email Work Email". Behind the scenes they're saved as text in double curly braces, such as `{{Find Email.Work Email}}`, which reads as "the **Work Email** that the step called **Find Email** produced".

## Where variables come from

A step can use data from anything that happens **before** it in the same path:

| Source | Where to find it in the picker | Example |
| - | - | - |
| **The trigger** | A group named after the trigger, e.g. "Email Received", "Custom Webhook", or your manual form | The sender of an incoming email; an answer from a manual form |
| **The current row** (list-based workflows) | **Record** | A column such as First Name or LinkedIn URL. Also **Record ID** and **Entire object** (the whole record) |
| **Earlier steps** | One group per step, headed by the step's name | The email address that "Find Email" found; the summary an AI Agent wrote |
| **The current loop item** (inside a For each) | A group named after the For each step | The item being processed in this loop |
| **Workspace secrets** | **Environment Variables** (marked "Secret") | An API key for an **Api Call** step |
| **Functions** | **Functions** tab | Today's date, a fallback when a value is empty, upper-case text |

Steps that come later, or that sit in a different branch, aren't offered: their values don't exist yet when this step runs. A step also can't use its own output ("You cannot use variables from the current node").

## Insert a variable

<Steps>
  <Step title="Click into the field">
    Open the step's settings and click the field where the value should go, at the spot where it should appear.
  </Step>

  <Step title="Open the picker">
    Click the **+** at the end of the field (**Insert variable**), or type **@**. Typing after the @ searches.

    The **Select variable or function** window opens: "Choose a variable or function to dynamically customize your workflow." It has a **Search** box and two tabs, **Variables** and **Functions**. Some fields add extra tabs, such as **Accounts**, **Columns** or **Date**.
  </Step>

  <Step title="Find the value">
    On **Variables**, open the group for the trigger, **Record**, or the step you want. Click the arrow next to an item to see what's inside it. Fields from a tested step show a sample value, so you can check you've got the right one.
  </Step>

  <Step title="Click it">
    The variable drops into the field as a chip, at your cursor. You can type text around it, and add as many variables as you need.
  </Step>

  <Step title="Save the step">
    Click **Save** in the step's panel.
  </Step>
</Steps>

**Working with chips**

* **Hover** a chip to see its exact path and, after a test, the value it had.
* **Click** a chip to change which field it points at.
* Click its **×** (**Remove variable**) to delete it.
* **@ inside a word doesn't open the picker**, so typing an email address like `ana@acme.com` is safe.
* To insert a whole object or list rather than one field inside it, click its **Insert** button in the picker.
* **Typing `{{` by hand doesn't create a variable reliably.** Always insert from the picker; it gets the name and spelling exactly right.

## Worked example: enrich a lead list, then send a personalised email

You have a list of webinar sign-ups with **First Name**, **Company** and **LinkedIn URL** columns. You want Connie to find each person's work email, then send each one a personal note.

<Steps>
  <Step title="Start with a list-based workflow">
    **Automations** → **New** → **New workflow** → **Run every row of a list**. On the **Records** tab, use **Actions** → **Import from List** (or **Import from CSV**) to bring in your sign-ups. Every row becomes one run.
  </Step>

  <Step title="Add a step that finds the email">
    On **Build**, click **+** and add **Find Email (LinkedIn URL)**. Rename it to "Find Email" so it's easy to recognise later. In its **Profile URL** field, click **+** → **Record** → **LinkedIn URL**. Click **Save**.
  </Step>

  <Step title="Test it once">
    Click **Test**, then **Run Test**. Check the cost in the confirmation and click **Run test**. The **Output** tab now shows what the step returns, for example **Work Email: [ana@acme.com](mailto:ana@acme.com)**. Testing is what makes the step's fields (with sample values) appear in the picker for later steps.
  </Step>

  <Step title="Add the email step">
    Click **+** below "Find Email" and add **Send Email**. Choose your connected Gmail or Outlook account as the sender, then fill in:

    * **To Email:** **+** → **Find Email** → **Work Email**.
    * **Subject:** type `Quick question for ` then **+** → **Record** → **Company**.
    * **Body:** type `Hi `, insert **Record** → **First Name**, type `,` and carry on with your message. Insert **Company** again wherever you mention the company.

    Click **Save**.
  </Step>

  <Step title="Run it">
    Click **Run** → **First row only** to try one real row, then check the result on the **Runs** tab. When you're happy, run **All rows**.
  </Step>
</Steps>

**What each row gets:**

| Row | First Name | Company | Found email | Email sent |
| - | - | - | - | - |
| 1 | Ana | Acme | [ana@acme.com](mailto:ana@acme.com) | To [ana@acme.com](mailto:ana@acme.com) · "Quick question for Acme" · "Hi Ana, …" |
| 2 | Ben | Globex | [ben@globex.com](mailto:ben@globex.com) | To [ben@globex.com](mailto:ben@globex.com) · "Quick question for Globex" · "Hi Ben, …" |
| 3 | *(blank)* | Initech | [sam@initech.com](mailto:sam@initech.com) | To [sam@initech.com](mailto:sam@initech.com) · "Quick question for Initech" · "Hi , …" |
| 4 | Dee | Umbrella | *(none found)* | Nothing is sent: **To Email** is required and there's no address to use, so the email step doesn't go through for this row |

Rows 3 and 4 show the two ways a variable goes wrong: an **empty** value prints as nothing, and a required field with nothing in it stops the step. Here's how to handle both.

**Fix row 3 with a fallback.** In the body, replace the First Name chip with a fallback: **+** → **Functions** → **Templates** → **Fallback for empty value**. It inserts `{= ifempty(value, "your company") =}`. Set the value to the **First Name** variable and the fallback text to `"there"`. Rows with a name still get "Hi Ana,"; rows without get "Hi there,".

**Handle row 4 with a branch.** Add a **Router** between the two steps with the condition **Find Email › Work Email** · **Is not empty**, and put **Send Email** on that branch. Rows with no email found go to the fallback branch instead of failing. See [Router](/automations/workflows/logic#router).

<Warning>
  **Testing Send Email sends a real email**, and testing Find Email spends real credits. While you build, test with your own details. See [Test a step](/automations/workflows/test-and-run#test-a-step).
</Warning>

Exact step and field names vary by action, which is why the steps above say "pick from the picker" rather than "type this".

## Worked example: branch on an AI step's answer

A **Run AI agent** step named "Qualify Lead" researches each company. In its **Output** settings you add a field called **Fit** and ask the agent to answer "Yes" or "No". Later steps can use that field like any other variable. It sits under the agent's **response**: `{{Qualify Lead.response.Fit}}`.

1. Add a **Router** after "Qualify Lead".
2. On **Flow 1**, add the condition **Qualify Lead › response › Fit** · **Equals** · `Yes`.
3. Put your outreach steps under Flow 1. Rows where the agent said "No" take the fallback branch.

Text comparisons are case-sensitive, so "yes" doesn't equal "Yes". Tell the agent exactly which words to use, or use an **AI Analyzer** step instead. See [Logic and control](/automations/workflows/logic).

## Read a step's output

To use a step's data, you need to know what it produces. There are four places to look:

* **The Test panel.** Click the step → **Test** → **Run test**. The **Output** tab lists every field and its value. **Copy output** copies it all.
* **The variable picker.** After a test, each field under the step's group shows a sample value.
* **The Runs tab.** After a real run, click any cell for **Cell Details**, with **Input** (what went in, with variables filled in) and **Output** (what came out). **Copy as JSON** copies it.
* **The record.** Click a row to open it and see every step's output for that row.

Before a step has been tested, the picker shows only the fields the action says it will return. Some actions, such as web scrapers that return a list, don't describe their fields in advance. Their group shows a link **run a test to map output variables from this action**; click it, run the test, then reopen the picker.

**Keep a value as a column.** In a cell's details, click **Add to column** next to any value, then **Create new column** or **Map to existing column**. You'll see a confirmation such as `Column "Work Email" created`.

## How variables are written

You rarely need to type these yourself, but knowing the pattern helps you read hover text and error messages.

| What it points at | Written as | Example |
| - | - | - |
| A field from an earlier step | `{{Step name.field}}` | `{{Find Email.Work Email}}` |
| A field inside another field | `{{Step name.field.subfield}}` | `{{Enrich Company.address.city}}` |
| The first item in a list | `{{Step name.list[0].field}}` | `{{Search People.results[0].name}}` |
| Every item in a list | `{{Step name.list[*].field}}` | `{{Search People.results[*].name}}` |
| A trigger field | `{{Trigger name.field}}` | `{{Custom Webhook.email}}` |
| A column on the current row | Shown as the column name under **Record** | First Name |
| The loop item (inside a For each) | `{{For each name.item.field}}` | `{{For each.item.url}}` |
| A workspace secret | `{{connie.env.KEY_NAME}}` | `{{connie.env.STRIPE_API_KEY}}` |
| A function | `{= function(...) =}` | `{= upper({{Find Email.Work Email}}) =}` |

**Rules worth knowing**

* **The step name comes first.** That's why renaming steps clearly helps.
* **Dots separate levels.** Spaces and colons inside a field name are fine (`{{Enrich Person.Job Title}}`).
* **Lists start at 0.** `[0]` is the first item, `[1]` the second. When you pick a field inside a list, the picker inserts `[0]` (the first item) by default.
* **Inside a For each,** `item` is the current item, `index` is its position (starting at 0) and `totalItems` is how many there are. From a step **after** the loop, use `{{Step name[*].field}}` to get every iteration's result, or `[2]` for the third.
* **What comes out:**
  * A field containing only one variable passes the value as it is: a number stays a number, a list stays a list.
  * A variable mixed with other text becomes text. A list of simple values reads like "Acme, Globex".
  * A whole object mixed into text comes out as raw data in curly braces. If you see that in an email, pick a single field inside the object instead.
* **Data is never treated as a variable.** If a scraped web page or an email contains `{{something}}`, it stays as plain text.

## Functions and fallbacks

Functions transform a value or supply one. Open the picker → **Functions**. Categories: **Templates**, **Variables**, **Logic**, **Operators**, **Math**, **Text**, **Date & Time**, **Arrays**, **Keywords**. Functions that need inputs open a small editor with **Cancel** and **Save**.

| You want | Use | Example |
| - | - | - |
| A default when a value is empty | **Fallback for empty value** (Templates) or `ifempty` (Logic) | `{= ifempty({{Enrich Person.First Name}}, "there") =}` |
| Today's date, nicely written | **Today's full date** (Templates) | "May 7, 2026" |
| A date a week from now | **Next week same day** (Templates) | "Wednesday, May 14" |
| Different text depending on content | **Conditional on text content** (Templates) | `{= if(contains(text, "interested"), "Follow up", "Skip") =}` |
| Change case | `upper`, `lower`, `capitalize` (Text) | "acme" → "ACME" |
| Round a number | `round` (Math) | 3.14159 → 3.14 |
| Count items in a list | `count` (Arrays) | 12 |

There's no separate "default value" setting on a chip. Use **Fallback for empty value** instead.

## Environment variables (secrets)

Store API keys and other secrets once in **Settings** → **Environment**, then insert them from the picker's **Environment Variables** group. The chip shows only the key's name, never its value. If the key is deleted, runs that use it fail. See [Environment variables](/developers/environment-variables).

## Renaming and deleting steps

* **Renaming a step** updates every variable that points at it, automatically.
* **Renaming a list column** is safe for variables inserted from the picker.
* **Deleting a step does not update variables that used it.** Those chips turn amber with a warning icon ("… doesn't point at anything anymore — the node or column it came from is gone. Delete the chip and pick a replacement."), and the card shows **1 unresolved variable** (or **N unresolved variables**). Open the step, remove the amber chip, and insert a replacement.

## Why is my variable empty?

First, what happens when a value isn't there:

| Situation | What happens |
| - | - |
| The value is blank (an empty column, or a step returned nothing) and the variable sits inside other text | It's filled in as nothing ("Hi , …"). The step still runs. |
| The value is blank, and the variable is the **only** thing in a **required** field | The step **fails** with `UNFILLED_VARIABLES: The following template variables could not be resolved: …`. |
| The value can't be found, in a **required** field | The step **fails** with the same `UNFILLED_VARIABLES` error. Retrying won't help until the cause is fixed. |
| The value is blank or can't be found, in an **optional** field | The variable is dropped and the step runs without it. |
| The value is blank or can't be found, in a **Router** condition | The run **fails**. It does not count as "false". Only **Exists**, **Does not exist**, **Is empty**, **Is not empty**, **Is true** and **Is false** handle a missing value safely. |

Then work through the usual causes:

| Cause | How to tell | Fix |
| - | - | - |
| **The source step didn't run for this row.** A Router sent the row down another branch, or an earlier step failed. | On **Runs**, the source step's cell says **Condition not matched**, **Skipped** or **Failed**. | Only use a step's data on the branch where that step runs. Fix the earlier failure. |
| **The step ran but returned nothing for that field.** For example, no email was found for this person. | Click the source cell on **Runs** and look at **Output**. | Add a fallback with `ifempty`, or a Router with **Is not empty** before the step that needs it. |
| **It's a list, not a single value.** | The picker shows the field inside a list (with `[0]` or `[*]`), or the variable points at the list itself. | Pick `[0]` for the first item, `[*]` for all items, or loop over it with **For each**. |
| **It's an output field of a Run AI agent step, written without `response`.** | The variable reads like `{{Qualify Lead.Fit}}`. | Use `{{Qualify Lead.response.Fit}}`: pick the field from inside the step's **response** in the picker. |
| **You're using a loop item outside the loop.** | The step that uses it sits after the For each, not inside it. | Inside the loop use `item`; after the loop use `{{Step name[*].field}}`. |
| **The step or column it pointed at was deleted.** | Amber chip; card says "1 unresolved variable". | Remove the chip and insert a new one. |
| **It was typed by hand and doesn't match.** | It shows as plain `{{…}}` text instead of a chip. | Delete it and insert it from the picker. |
| **The record's column is blank.** | Open the row on **Records**. | Fill the column, or add a fallback. |
| **In a test only: the earlier step hasn't been tested.** A test uses each earlier step's **last test result**, not a real run. | The Test **Input** tab asks you to fill the value in. | Test the earlier steps first, in order, or type a value into the **Input** tab. |
| **In a test only: it used a different row.** A test uses the **newest** record on the Records tab. | The value belongs to a row you weren't expecting. | Pick the record you want with **Search a record…** in the **Input** tab. |
| **A secret is missing.** | The run fails mentioning `WORKSPACE_ENV_MISSING`. | Add the key in **Settings** → **Environment**. |

## Good to know

* Variables only offer steps **above** the current step in the same path.
* `{{table.row_id}}` no longer works. Use **Record ID** from the **Record** group.
* In a **Test**, a variable holding a whole object is sent as text. A real run passes it as an object. If a test behaves slightly differently from a real run, this may be why.
* In an **AI Agent** step's **Task** field, typing **/** opens your [skills](/skills/overview), not variables.

## Troubleshooting

<AccordionGroup>
  <Accordion title="UNFILLED_VARIABLES: The following template variables could not be resolved">
    **Why:** A required field (or a Router condition, or a For each **Iterate Over**) uses a variable that had no value for this row, or a blank value where the variable is the only thing in the field. Common causes: the source step was on a branch that didn't run, it returned nothing, the variable points at a list, or it was typed by hand.
    **Fix:** Click the failed cell on **Runs** to see which variable it names. Check that step's cell for the same row. Then re-insert the chip from the picker, add a fallback with **Fallback for empty value**, or guard the step with a Router condition **Is not empty**. Use **Retry this row** once fixed.
  </Accordion>

  <Accordion title="A chip turned amber and the card says 1 unresolved variable">
    **Why:** The step or column the chip pointed at was deleted.
    **Fix:** Open the step, remove the amber chip with its ×, insert a replacement from the picker, and **Save**.
  </Accordion>

  <Accordion title="The picker shows no fields under a step">
    **Why:** The step hasn't been tested, and its action doesn't describe its fields in advance.
    **Fix:** Click **run a test to map output variables from this action** (or open the step and click **Test**), run the test, then reopen the picker.
  </Accordion>

  <Accordion title="The variable is empty when I test, but the step works in real runs">
    **Why:** A test uses earlier steps' **test** results and the newest record. If an earlier step hasn't been tested, there's no sample value.
    **Fix:** Test the earlier steps in order, pick a sample record in the **Input** tab, or type a value there.
  </Accordion>

  <Accordion title="My email contains text in curly braces instead of a value">
    **Why:** The variable points at a whole object, which comes out as raw data when mixed with text.
    **Fix:** Open the picker, expand that object and choose the single field you want.
  </Accordion>

  <Accordion title="The Router failed instead of taking the fallback branch">
    **Why:** A condition like **Equals** or **Contains** on a value that doesn't exist is an error, not "false".
    **Fix:** Add an **Exists** or **Is not empty** check first, or make sure the source step runs on this path.
  </Accordion>

  <Accordion title="You cannot use variables from the current node">
    **Why:** A step can't use its own output.
    **Fix:** Use data from an earlier step, or split the work into two steps.
  </Accordion>

  <Accordion title="Only array variables can be used as an iterator">
    **Why:** A For each's **Iterate Over** needs a list, and the variable you chose is a single value (shown as **not an array**).
    **Fix:** Pick a variable that holds a list.
  </Accordion>

  <Accordion title="Arrays are not supported for this field">
    **Why:** The field takes one value, but the variable is a list.
    **Fix:** Use **Wrap in For Each** to run the step once per item, or pick `[0]` for just the first item.
  </Accordion>

  <Accordion title="Fill in the required fields to run this test">
    **Why:** The test needs values that earlier steps haven't produced yet.
    **Fix:** Test the earlier steps first, or type values into the Test **Input** tab.
  </Accordion>

  <Accordion title="The run failed with WORKSPACE_ENV_MISSING">
    **Why:** A step uses an environment variable that doesn't exist in this workspace.
    **Fix:** Add the key in **Settings** → **Environment**. See [Environment variables](/developers/environment-variables).
  </Accordion>

  <Accordion title="A loop item is empty in a step after the loop">
    **Why:** `item` only exists inside the For each.
    **Fix:** Move the step inside the loop, or use the loop step's results with `[*]` (all) or an index such as `[0]`.
  </Accordion>
</AccordionGroup>

## Related

* [Build a workflow](/automations/workflows/build) — add and configure steps
* [Logic and control](/automations/workflows/logic) — Router conditions, For each loops
* [Test and run](/automations/workflows/test-and-run) — test steps and read results
* [Webhooks](/automations/workflows/webhooks) — use data sent by outside apps
* [Environment variables](/developers/environment-variables) — store secrets for workflows
* [Workflow troubleshooting](/automations/workflows/troubleshooting) — more fixes


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.