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

# Variables

> Variables are typed flow state. They carry user choices, drive conditional UI, and can be seeded from the host app through the SDK.

A variable is a named, typed piece of state that lives inside a flow. Variables let a flow remember things: which plan a user tapped, how far they are through a quiz, whether a switch is on. You can show a variable in text, branch on it with [dynamic values](/editor/dynamic-values), change it with [actions](/editor/interactions-and-actions), and read values your app passes in through the SDK.

Variables hold state only. They never directly control a property. To make a property react to a variable, you use a [dynamic value](/editor/dynamic-values).

<Note>
  The full variable manager described here is an **Advanced mode** surface. In [Simple mode](/editor/editing-modes) variables are managed for you (a question or input block creates and writes its own) and shown read-only in the [Data collected](/editor/screens-panel#the-data-collected-card) card. Switch to Advanced to add, edit, or delete variables by hand.
</Note>

## Variable types

Every variable has one of four types:

| Type                     | Holds                       | Example                       |
| ------------------------ | --------------------------- | ----------------------------- |
| **String (Text)**        | Text                        | `"monthly"`, a name, an email |
| **Number**               | A number                    | `0`, `42`, a score            |
| **Boolean (True/False)** | `true` or `false`           | a toggle, a "has agreed" flag |
| **List (Array)**         | A homogeneous list of items | selected options              |

A **List** variable also has a **List Item Type** (Text Items, Number Items, or Boolean Items). Every item in the list must be that type. A list of text cannot also hold numbers.

## Where a variable's value comes from

When you create a variable you pick a **Variable Type** (this is separate from the data type above). It decides where the value comes from and whether the flow can change it.

| Variable Type     | Source                                         | Editable in the flow?      | Use it for                                                   |
| ----------------- | ---------------------------------------------- | -------------------------- | ------------------------------------------------------------ |
| **Flow State**    | A constant initial value you set in the editor | Yes, actions can change it | choices, counters, toggles, anything the flow itself updates |
| **App Parameter** | A value read from the SDK context at runtime   | No, it is read-only        | user id, plan, app version, anything the host app knows      |
| **Calculated**    | Computed from other variables with a formula   | Recomputed automatically   | a derived number, like "27 years" from earlier answers       |

* **Flow State** is mutable. Its starting value is the **Initial Value** you type in the dialog. Actions like [Set Variable](/editor/interactions-and-actions) can change it while the flow runs.
* **App Parameter** is read-only. The SDK supplies its value from the context your app passes in (see variables and SDK context for [iOS](/ios-sdk/variables-and-context) or [Expo](/expo-sdk/variables-and-context)). The editor derives the lookup path from the variable name (lowercased, with spaces replaced by underscores), so a name like `user.id` becomes a nested path lookup. If the app does not provide a value, the **Default Value** is used as a fallback.
* **Calculated** is a value computed from other variables, set with a guided formula builder. See [Calculated values and answer scores](/editor/calculated-values).

In the [Data collected](/editor/screens-panel#the-data-collected-card) summary these three appear as **Captured in flow** (Flow State), **From app** (App Parameter), and **Preset value** (a fixed constant).

Variables are session-scoped. Their values last for one run of the flow and reset the next time the flow is presented.

<Note>
  You do not set "writable" or "lifecycle" by hand. The editor sets them for you: Flow State is writable, App Parameter is read-only, and both are session state.
</Note>

## Create a variable

<Steps>
  <Step title="Open the Variables tab">
    In the left sidebar, click **Variables**. The panel lists every variable in the flow with its type and current value.
  </Step>

  <Step title="Click Add">
    Click **Add** (top right of the panel) to open the **Create Variable** dialog.
  </Step>

  <Step title="Name it and pick a type">
    Enter a **Name** (for example `selected_plan`) and choose a **Type**. For a **List**, also choose a **List Item Type**.
  </Step>

  <Step title="Choose Flow State or App Parameter">
    Set **Variable Type** to **Flow State** (a value the flow owns and changes) or **App Parameter** (a value the app provides through the SDK).
  </Step>

  <Step title="Set the starting value">
    For Flow State, set the **Initial Value**. For App Parameter, set a **Default Value** to fall back to when the app does not provide one.
  </Step>

  <Step title="Limit the range (Number or List)">
    For a **Number**, set **Allowed range** — a **Min** and/or **Max**. Leave a box blank for no limit on that side. See [Limiting a number's range](#limiting-a-numbers-range).

    For a **List**, set a **Selection limit** — the most answers a multi-select question bound to it will accept. Leave it blank for no limit. See [Limiting how many answers can be picked](#limiting-how-many-answers-can-be-picked).
  </Step>

  <Step title="Create">
    Click **Create Variable**. The variable appears in the list and is ready to use. To change it later, hover the row and click the edit icon.
  </Step>
</Steps>

## Limiting a number's range

A **Number** variable can declare an **Allowed range**. Set **Min** to `0` and
**Max** to `100` on an `age` variable and the value can never leave 0–100 —
whatever tries to change it:

* a stepper's **−** / **+** buttons
* a slider, picker, or ruler
* a calculated value or a Set Variable action
* a value your app passes in through the SDK
* someone typing into a number field

You do not have to guard each one. The range lives on the variable, and the SDK
enforces it on every write. Out-of-range values snap to the nearest limit.

<Tip>
  Bounds are the reason a [stepper block](/editor/blocks) is safe: its **−** and
  **+** grey out at the limits, and even if they did not, the value still could
  not escape the range.
</Tip>

Notes:

* **Number only.** To limit the *length* of text, use an input field's validation instead.
* Leave **Min** or **Max** blank for no limit on that side.
* **Min** must be less than or equal to **Max**; the editor blocks saving an inverted range.
* Typed number fields are the one place a clamp is announced: the field snaps into range when the user leaves it and shows "Must be between 0 and 100". (Clamping as they type would make values below the minimum impossible to enter.)
* Requires an app running a current SDK build. Older SDKs ignore the range.

## Limiting how many answers can be picked

A **List** variable can declare a **Selection limit** — the "pick up to 2" of a
multi-select question. It is the list's counterpart to a number's range, and it
works the same way: the limit lives on the variable, and the SDK enforces it on
every write, so nothing can push more answers into the list than you allow.

You can set it in two places, and they are the same setting:

* On the **variable**, via **Selection limit** in the variable editor.
* On the **question**, via **Max selections** in a multi-select block's
  **Selection limit** section. This writes to the bound variable for you.

Once the limit is reached, the remaining unpicked answers grey out and stop
responding to taps. The answers already picked stay lit and stay tappable —
deselecting one is how the user gets back under the limit, and the rest
immediately become pickable again.

<Tip>
  The limit does not write your copy. If the question says "Pick up to 2", that is
  a normal text block you edit yourself — so you can word it however you like.
</Tip>

Notes:

* **List only**, and multi-select only. A single-select question stores one
  answer in a **Text** variable, so there is nothing to cap.
* **0 or blank = unlimited**, which is the default. Existing questions are
  unaffected until you set a limit.
* Setting a limit greys out the extra answers automatically. If you had restyled
  an answer's background, the greyed state uses your theme's neutral surface
  colour and leaves your selected colour alone.
* Requires an app running a current SDK build. Older SDKs ignore the limit and
  the question stays unlimited.

### Keys vs names

When you create a variable from this dialog, the editor stores your typed name as the variable's **label** and assigns it a generated **key** like `var_ab12cd`. The key is what `{{ }}` interpolation and conditions actually use.

You rarely type keys by hand:

* Text fields have a `{}` **Insert variable** button that lists variables by name and splices the correct `{{key}}` token at your cursor.
* The condition builder and the Set Variable action both pick variables from a dropdown that shows names, not raw keys.

Variables created by [blocks](/editor/blocks) (for example a choice block's `selected_*` variable) use readable keys instead of generated ones.

## Using variables

There are four ways to use a variable:

1. **Interpolate it in text.** Anywhere text accepts it, `{{key}}` is replaced with the variable's current value. For example, a heading of `Welcome back, {{first_name}}` renders the value of `first_name`. Use the `{}` button to insert the right token.
2. **Drive a dynamic value.** Feed a variable into a condition to switch a property's value. See [Dynamic values](/editor/dynamic-values).
3. **Change it with an action.** The [Set Variable](/editor/interactions-and-actions) action applies a typed operation (set, increment, toggle, add to list, and so on). Only Flow State variables appear in its picker, because App Parameters are read-only.
4. **Bind it to a component input.** A component prop or a [custom component](/editor/custom-components) input can read from a variable instead of a fixed value.

## Example

A simple plan picker:

1. Create a Flow State **String** variable named `selected_plan` with an **Initial Value** of `monthly`.
2. On a "Monthly" button, add a [Set Variable](/editor/interactions-and-actions) action: `selected_plan` -> **Set to value** -> `monthly`. On a "Yearly" button, set it to `yearly`.
3. On a "Continue" button, make the label dynamic: when `selected_plan` equals `yearly`, show `Continue with Yearly`, otherwise `Continue`. See [Dynamic values](/editor/dynamic-values).
4. Create an **App Parameter String** variable named `user.id`. Use `{{user.id}}` in a [Trigger Event](/editor/interactions-and-actions) property, or read it on the SDK side for analytics. Your app supplies its value through the SDK context.

The schema for `selected_plan` looks like this:

```json theme={null}
{
  "key": "selected_plan",
  "label": "selected_plan",
  "type": "string",
  "scope": "global",
  "lifecycle": "session",
  "source": { "kind": "constant", "value": "monthly" },
  "writable": true
}
```

## Common mistakes

* **Wrong type.** Storing a number as a String breaks numeric comparisons (`greater than`, `less than`) because those operators only work on Number variables. Pick the type that matches how you will use the value.
* **Referencing a key that does not exist.** A missing or misspelled variable in `{{ }}` resolves to an empty string, so the text just disappears. Insert variables with the `{}` button to avoid typos, and remember that a manually created variable's key is `var_...`, not the name you typed.
* **Expecting an App Parameter to change mid-flow.** App Parameters are read each time the flow starts. They do not update on their own while a flow is on screen. The app must supply fresh context (see [Variables and SDK context](/ios-sdk/variables-and-context)).
* **Trying to Set Variable on an App Parameter.** Read-only variables do not appear in the Set Variable picker, and a write to one is ignored at runtime.

## Troubleshooting

| Symptom                                | Likely cause                                              | Fix                                                                                             |
| -------------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `{{ }}` renders nothing                | The key does not exist or is misspelled                   | Insert the variable with the `{}` button; check the key in the Variables panel                  |
| A number comparison never matches      | The variable is a String, not a Number                    | Recreate or edit it as a Number                                                                 |
| An App Parameter is always its default | The app did not send that key, or the path does not match | Confirm the path matches the SDK context your app sends ([iOS](/ios-sdk/variables-and-context)) |
| A Set Variable action does nothing     | The target is read-only (an App Parameter)                | Use a Flow State variable instead                                                               |

## Related pages

* [Calculated values and answer scores](/editor/calculated-values)
* [Dynamic values](/editor/dynamic-values)
* [Interactions and actions](/editor/interactions-and-actions)
* [Navigation and branching](/editor/navigation-and-branching)
* [Variables and SDK context (iOS)](/ios-sdk/variables-and-context)
