---
title: "Flexie Scripting: Language Basics"
url: https://flexie.io/resources/flexie-scripting/language-basics
description: "Everything you need to read and write Flexie Scripting confidently. Values, record fields, conditions, loops, operators, tests, comments, and the always-available globals."
---

# Language Basics

Last updated 23 May 2026

![A short Flexie Scripting template with placeholders, an if block, and a for loop](https://flexie.io/image/resources/flexie-scripting-language-basics.png)

Flexie Scripting is small on purpose: values, record fields, conditions, loops, and operators. Once you know that handful of shapes, you can read or write any script you'll encounter in templates, workflow actions, and dynamic endpoints.

## Printing a value

Use `{{ ... }}` to print something:

```
{{ "Hello" }}            → Hello
{{ 42 }}                 → 42
{{ first_name }}         → the first_name of the current record

```

Anything between the braces is worked out first, then printed. You can combine text and values by stringing pieces together, but the usual way is to write plain text and only drop into `{{ }}` where a value belongs:

```
Dear {{ first_name }} {{ last_name }},

```

## Reading record fields

When a script runs, it is given the **record it is working on**, a contact, a deal, an invoice, and so on. You read that record's fields **directly, with no prefix**:

```
{{ first_name }}
{{ email }}
{{ total_incl_tax }}
{{ stage }}

```

The name in the braces is the field's **alias** (its system name), which you can find next to the field itself in the admin field setup (Custom Fields).

> There is **no** built-in `contact.`, `deal.`, or `invoice.` global. Writing `{{ contact.first_name }}` does **not** work, the current record's fields live at the top level. A prefixed path like `{{ X.field }}` is only valid when `X` is a name you have created yourself (see below).

### Variables for any **other** record

To work with a record that is **not** the current one, fetch it with a look-up function (such as `findOne`) and assign the result to your own variable. From then on you read its fields under that variable name:

```
{% set primary = findOne("contact", "email", email) %}
{{ primary.first_name }}, {{ primary.phone }}

```

Here `primary` is a name **you** chose, it could be `contact`, `match`, `c`, or anything else. The dot-path is valid because `primary` is now a defined variable holding an associative result.

### Data from the trigger and earlier steps (in a workflow)

Inside a workflow, anything the trigger carried or earlier steps stored is under the `__data` namespace:

```
{{ __data.incoming_email.subject }}
{{ __data.last_task.due_date }}

```

See [Where it runs & its limits](https://flexie.io/resources/flexie-scripting/where-it-runs-and-limits) for what is available in each context.

> **Tip: square brackets for awkward names.** If a key has spaces or special characters, or is held in a variable, use brackets: `{{ row['field name'] }}` or `{{ row[chosenField] }}`.

## Setting your own variables

Use `{% set %}` to name a value once and reuse it:

```
{% set fullName = first_name ~ " " ~ last_name %}
{% set vatRate = 0.20 %}

Hello {{ fullName }}, VAT is {{ vatRate * 100 }}%.

```

The `~` operator joins (concatenates) text together.

## Making decisions, `if`

Show or compute something only when a condition holds:

```
{% if amount > 10000 %}
  Priority deal, assign to a senior rep.
{% elseif amount > 1000 %}
  Standard deal.
{% else %}
  Small deal, handle via self-service.
{% endif %}

```

The short, inline form (a "ternary") is handy inside `{{ }}`:

```
{{ amount > 1000 ? "Large" : "Small" }}

```

And to supply a fallback when something is empty, use `??`:

```
{{ phone ?? "no phone on file" }}

```

## Repeating, `for`

Loop over a list of records or values:

```
{% set list = findMany("invoice", "contact_id", id, "due_date", "ASC", 50) %}
{% for inv in list %}
  Invoice {{ inv.number }}, {{ inv.total_incl_tax | number_format(2) }}
{% endfor %}

```

Inside the loop, `inv` is the **loop variable** (a name you chose) holding the current row, so `inv.number` is valid even though `invoice.number` directly would not be.

Inside a loop you get a `loop` helper with useful counters:

```
{% for item in items %}
  {{ loop.index }}. {{ item.name }}{% if not loop.last %}, {% endif %}
{% endfor %}

```

| Helper      | Meaning                     |
| ----------- | --------------------------- |
| loop.index  | Current pass, starting at 1 |
| loop.index0 | Current pass, starting at 0 |
| loop.first  | True on the first pass      |
| loop.last   | True on the last pass       |
| loop.length | Total number of passes      |

## Operators you can use

### Maths

`+` `-` `*` `/` `%` (remainder) `//` (whole-number division) `**` (power)

```
{{ (price * quantity) * (1 + vatRate) }}

```

### Comparisons

`==` (equal) `!=` (not equal) `<` `>` `<=` `>=` and membership tests `in` / `not in`:

```
{% if stage_id in [4, 5] %}...{% endif %}

```

### Logic

`and` `or` `not`

```
{% if email and not unsubscribed %}...{% endif %}

```

### Joining text

`~` joins values into one string:

```
{{ "Hi " ~ first_name ~ " " ~ last_name ~ "!" }}

```

## Tests, checking the shape of a value

```
{% if phone is defined %}...{% endif %}
{% if notes is empty %}...{% endif %}
{% if items is iterable %}...{% endif %}

```

Common tests: `is defined`, `is null`, `is empty`, `is iterable`, `is even`, `is odd`.

## Comments

Anything between `{# ... #}` is ignored, never printed, never run. Use it to leave notes for whoever edits the template next:

```
{# Discount only applies to renewals, confirmed with finance #}

```

## Inserting a field with the merge-field picker

Some editors (for example the email or template builder) offer a **merge-field picker**, a menu of available fields you can click to drop a value into your content without typing. That is the click-driven way to insert a single field value.

When you need anything beyond a plain value, formatting, a fallback, a condition, a calculation, a function, write Flexie Scripting with `{{ }}`:

```
{{ first_name | capitalize }}
{{ phone ?? "no phone on file" }}

```

The `{{ }}` form is the powerful one and is the focus of this guide.

## Global values always available

A few values are present in every script:

| Value            | What it holds                                                                                                                                 |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| LOGGED\_IN\_USER | The signed-in user running the action (their id, name, email, phone, role, timezone). Empty when no one is signed in (e.g. a background job). |
| NULL / EMPTY     | Convenient names for "nothing" or an empty string.                                                                                            |

```
Prepared by {{ LOGGED_IN_USER.full_name }} on {{ now("Y-m-d") }}.

```

Administrators can also define **custom global variables** (a company name, a support address, a default VAT rate) that then become available by name in every template. Ask your administrator what globals are defined for your account.

## Next steps

* [Function reference](https://flexie.io/resources/flexie-scripting/function-reference): the full toolbox of built-in functions.
* [Filters & collections](https://flexie.io/resources/flexie-scripting/filters-and-collections): reshaping single values and whole lists.
