---
title: "Flexie Scripting: Where It Runs & Its Limits"
url: https://flexie.io/resources/flexie-scripting/where-it-runs-and-limits
description: "Which data a script can see depends on where it runs, and what it can do is bounded by a safety sandbox. This page sets both expectations clearly."
---

# Where It Runs & Its Limits

Last updated 23 May 2026

![Flexie Scripting running inside a sandbox, with access to the current record, __data, and a documented toolkit](https://flexie.io/image/resources/flexie-scripting-where-it-runs-and-limits.png)

## The record a script is given

Every place that evaluates Flexie Scripting hands it a **record to work on** plus the always-available [global values](https://flexie.io/resources/flexie-scripting/language-basics). What that record is depends on the context:

| Where it runs                          | The record it is given                           |
| -------------------------------------- | ------------------------------------------------ |
| An email template sent about a contact | that contact                                     |
| An invoice PDF template                | that invoice                                     |
| A quote PDF template                   | that quote                                       |
| A workflow action on a deal            | that deal                                        |
| An SMS template                        | the message's recipient record                   |
| A snippet                              | the same record as the template that included it |

You read that record's fields directly, `{{ first_name }}`, `{{ total_incl_tax }}`, with **no `record.` prefix**. To work with any **other** record (a related lead, a recent invoice, the linked account), use a [look-up function](https://flexie.io/resources/flexie-scripting/function-reference) such as `findOne`, `findMany`, or `getAccountContacts`, and assign the result to a variable of your own.

> **Built-in related arrays.** In a workflow whose record type has standard relations, for example a Deal, which has one linked Account and many linked Contacts, those related records are also exposed automatically as arrays under the plural name: `{{ contacts[0].first_name }}`, `{{ accounts[0].account_name }}`. Use `[0]` to take the first (or only) one.

## The `__data` namespace (inside workflows)

When a script runs **inside a workflow**, it also has access to everything the workflow has gathered so far, under a single namespace called `__data`.

This is how data flows from one step to the next. When a workflow is triggered by an incoming signal (an email arrives, a form is submitted, a webhook is received), or when an earlier action stored a value, that information is available to every later step as `{{ __data.something }}`:

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

```

The full mechanics of `__data`, what each trigger provides, and how to store your own values for later steps, are covered in [Passing data between steps](https://flexie.io/resources/workflows/passing-data-between-steps). For dynamic endpoints, the incoming request body is also exposed this way, see [Dynamic Endpoints, Receiving data](https://flexie.io/resources/dynamic-endpoints/receiving-data).

## Result limits

To keep templates fast and safe, look-ups that can return many rows are **capped at 1,000 results**:

* `findMany(...)` defaults to 1,000 and will not exceed it.
* `query(...)` is capped at 1,000 rows.

If you need totals across more than that, total at the source with `findSum`, `findCount`, `findMin`, `findMax` (which compute in the database) rather than pulling every row back and adding them up in the template.

`query(...)` is **read-only**: only `SELECT` and `WITH` are accepted. Anything that would change or remove data (`INSERT`, `UPDATE`, `DELETE`, `DROP`), and `SELECT *`, are rejected. You will get an `{"error": "…"}` result instead.

## The safety sandbox

Flexie Scripting runs inside a protected sandbox. This is what keeps it safe to expose in templates and workflow fields. The boundaries:

### Only the documented toolkit exists

Only the **functions**, **filters**, and **tags** listed in this section are available. If a script uses anything else, it stops with an error rather than doing something unexpected. The available tags are exactly:

* `{% if %}` / `{% elseif %}` / `{% else %}` / `{% endif %}`
* `{% for %}` / `{% endfor %}`
* `{% set %}`
* `{% snippet "alias" %}`

(There is no "include a file," "import," "macro," or similar, those are deliberately not available.)

### You read data, you do not call methods on it

Records are provided to your script as plain data, so reading fields works exactly as shown: `{{ email }}`, `{{ total_incl_tax }}`, `{{ row['some key'] }}` (where `row` is a variable you set from a look-up). What you **cannot** do is call methods or reach internal properties on objects; the sandbox does not permit it. Everything you need is exposed either as readable record data or as one of the documented functions.

### It cannot reach outside Flexie

A script cannot read or write files on the server, run programs, or use anything that has not been deliberately made available. The only ways a script touches the outside world are through the dedicated, controlled functions documented here (for example a webhook is sent by a **workflow action**, not by the template language itself).

### Errors are contained

When a script fails, a bad function name, malformed input, it raises an error in that one place. It does not crash the surrounding page or corrupt data.

## Practical implications

* **Test with a real record.** Because a script depends on the record it is given, always preview against a real example before going live. Workflows have a built-in test mode (see [Scheduling, testing & troubleshooting](https://flexie.io/resources/workflows/scheduling-testing-troubleshooting)); templates have a preview.
* **Guard against empty values.** A field can be blank. Use `default(...)`, `coalesce(...)`, or an `{% if %}` so a missing value does not produce ugly output.
* **Prefer database-side totals.** `findSum` and `findCount` over fetching rows and adding them up, faster, and never hits the 1,000-row cap.
* **Keep heavy look-ups out of tight loops.** Calling `findMany` inside a `for` loop that itself runs over many records multiplies the work. Fetch once, then loop.

## Next steps

* [Recipes](https://flexie.io/resources/flexie-scripting/recipes): complete, real examples that respect these limits.
* [Function reference](https://flexie.io/resources/flexie-scripting/function-reference): the full toolkit.
