Skip to content

Formulas

Check answers against each other, right on the form, with one line. end_date > start_date tells a visitor at once that their dates are backwards, with no server of your own and no code to host.

Write one

Open a question in the Builder, then Validations, and choose Formula. Write what must be true, and the message to show when it isn’t. The builder reads your formula back in words, and tells you what to fix as you type.

Text
end_date > start_date

To show or hide a question, open Conditions and choose Advanced: write a formula. A formula can sit in a group with your other rules.

Text
quantity * unit_price >= 100

Add a few tries under Try it to see the answer before you publish. They’re saved for whoever edits the form next, and visitors never see them.

What you can write

You writeIt means
a + b, a - b, a * b, a / b, a % bArithmetic
a == b, a != b, a < b, a <= b, a > b, a >= bCompare two things
country in ["CA", "US"]One of these
a and b, a or b, not aCombine
contact.phoneA question inside a group
items[].price, items[0].priceA column of a repeated group, or one row
`first-name`A key with a hyphen, a leading digit, or the name of a word or function

Text goes in double or single quotes, and a phone’s curly quotes work too. There’s no assignment, no loop, and no way to reach anything outside this form’s own answers.

Functions

GroupFunctions
Checksluhn(text), isEmpty(value), isEmail(text), isUrl(text), isPostalCode(text, "CA"), isPhone(text)
Textlen, lower, upper, trim, contains, startsWith, endsWith, concat
Numbersround(number, places), floor, ceil, abs, min, max
Listscount, sum, contains, unique
Datesdate(text), today("America/Toronto"), daysBetween(from, to), dateOf(moment, "America/Toronto"), year, month, day
Logicif(condition, a, b), coalesce(a, b, ...)

luhn says a number is well formed, nothing more. Don’t collect card numbers in a form.

When a question has no answer

An unanswered question is unknown, so an optional question never fails because it’s empty.

CaseResult
Arithmetic with an unanswered questionUnknown
<, <=, >, >= with an unanswered questionUnknown
==, !=, inAlways answers. a == null is true when a is unanswered, and a != "CA" is true too
A validation formula that ends up unknownPasses. Required is what demands an answer
A condition that ends up unknownFalse. The rule doesn’t apply yet
Dividing by zeroUnknown

So country == "CA" as a validation fails on an empty answer. To allow one, write isEmpty(country) or country == "CA".

Numbers and dates

Numbers are exact decimals, so 0.1 + 0.2 == 0.3 is true everywhere, up to 30 significant digits and 12 decimal places. Dates are calendar dates, so no time zone can move one. today needs a named time zone, because a business’s today and a visitor’s aren’t always the same day. A date and a number can’t be compared, and neither can a date and text until date(field) reads the text as a date.

Who can read a formula

Visitors’ browsers run a formula for instant feedback, so anyone can read it. The builder warns you when one holds a value that could matter, such as a code. Don’t put a secret in one.

For a check that shouldn’t show its logic, turn on Check only when submitted, and keep it private. Nothing about it reaches the browser, and the error comes from our server when the form is sent. Even so, someone can try guesses and read the answers, so for a real list of secrets, check it on your own server.

Showing or hiding a question has to work as people type, so those formulas can’t be private. A formula that reads a list is checked only when the form is sent, and can’t show or hide a question.

Our servers repeat every check, whatever a browser says, so skipping a formula in the browser gets nobody past it. A formula can’t read a password field, a recipient, a sender, or a credential.

When a question changes

If you rename a question or move it into a section, the builder updates your formulas when it saves. If you delete one that a formula reads, the builder names what depends on it, and the formula shows as Broken until you fix it. A form can’t be published with a broken formula, and the people on an earlier version keep that version.

A recipient’s condition isn’t part of the published version. A formula there reads each answer as it came in, so one that names a renamed question reads it as unanswered for submissions sent before the change.

Limits

A formula can be 1,000 characters, 500 parts and 32 levels deep, and a form can hold up to 64 KB of them. A list counts its first 1,000 items, and past that the formula is unknown. For anything bigger, split it into two rules.

Going further

Formulas as JSON

A formula goes wherever a condition goes, and as a rule’s validator. Send only text, since the server writes the rest. A rule of nothing but formulas needs no when.

JSON
{
  "validationRules": [
    {
      "id": "dates",
      "then": [
        {
          "path": "end_date",
          "validator": {
            "type": "formula",
            "formula": { "text": "end_date > start_date" },
            "code": "END_BEFORE_START",
            "message": "The end date has to be after the start date."
          }
        }
      ]
    }
  ],
  "visibilityRules": [
    {
      "id": "budget",
      "path": "budget",
      "default": "hidden",
      "state": "visible",
      "when": { "formula": { "text": "quantity * unit_price >= 100" } }
    }
  ]
}

A public schema sends a formula as { "v": 1, "tree": { ... } }, with no text. v is the language version the formula was saved in, and it keeps its meaning for good. A version’s functions and results don’t change.

In your own code

validateFormeriePublicFields runs a schema’s formulas once the evaluator has loaded, and forms drawn by the SDK load it by themselves.

TypeScript
import { loadFormerieFormulas, validateFormeriePublicFields } from "@formerie/client";

await loadFormerieFormulas();
const result = validateFormeriePublicFields(schema, answers);

The package ships the language’s specification as @formerie/client/conformance/formula-v1.json. Every entry runs on the browser and on our servers.

Through MCP

get_form_schema lists the grammar and functions. validate_form_definition checks a formula without saving, and formulaSamples tries one against answers, storing nothing. update_form saves one in definitionPatch. Through MCP or the API, a formula that doesn’t check out is refused with its FORMULA_ code, rather than saved as a draft to fix later. See Error codes.