Every field in the form builder has a Business rules section with two boxes: Formula computes the field's value from other fields, and Visible when shows or hides the field based on other fields. Together they turn a flat checklist into a form that calculates totals, reveals follow-up questions, and fills in dates by itself.
Both boxes hold a small piece of JavaScript. You do not need to be a programmer to use them — the form patterns page has copy-ready examples for the common cases — but knowing the five rules below explains why they behave the way they do.
Rule 1: always return a value
Each box is the body of a small function. The word return hands back
the result — without it, nothing comes out.
// Formula: the field's value becomes whatever you return.
return fields.quantity * fields.unit_price;// Visible when: return true to show the field, false to hide it.
return fields.status === 'Failed';Rule 2: reach other fields with fields. + the field id
Every field has an id in snake_case (lowercase words joined by
underscores), shown in the builder. Read another field's current value
as fields.its_id — always with a dot, exactly as written:
return fields.first_name;Write fields.first_name, never fields['first_name'] — the bracket
style returns the same value, but the builder cannot see the
connection, so the formula stops updating when that field changes.
A field may be empty while the user works, so guard your math:
fields.quantity || 0 means "the quantity, or zero if it is empty."
Rule 3: three scopes — fields, parent, top
fields— the values at the same level as the field you are editing. At the top of the form, that is the whole form; inside a subform row, it is that row only.parent— one level up. Inside a row,parent.site_namereads the main form'ssite_name.top— the root form, from any depth.
Two more values sit alongside these, and both are covered below: meta,
the row's position inside a repeating section, and fieldscroll, what
the platform itself knows about the session — starting with who is
filling the form.
One direction is closed on purpose: a row cannot read its sibling rows. Totals across rows belong on the main form, where the whole list is in scope — see the grand total pattern.
Rule 4: a subform row knows its position with meta
Inside a subform row, one more value is in scope: meta. It holds the
row's place in the list, so a formula can number rows or treat the
first or last one differently.
meta.index— the row's position, counting from zero (the first row is0, the second is1).meta.count— how many rows the list has right now.meta.isFirstandmeta.isLast— true on the first and last rows.
The common use is a one-based label:
return `Sample ${meta.index + 1}`;meta exists only inside a subform row. At the top of the form there
is no surrounding list, so meta is empty there. If a field can appear
both at the top and inside a row, check for meta first:
return meta ? `Sample ${meta.index + 1}` : 'Sample';Positions stay correct as the list changes: add or remove a row and the
numbers recompute, so there are no gaps. meta.index is the position in
the row's own list — a row in a nested subform sees its place in the
list directly around it, not the outer one.
Rule 5: mark computed fields read-only
Turn on Read only for any field with a formula. The formula overwrites the field's value when it recalculates, so anything a user typed there would be lost anyway — read-only makes that honest.
Rule 6: know when things recalculate
| What | When it updates |
|---|---|
Visibility (Visible when) | Immediately, on every change |
| Formulas at the top of the form | When the user leaves the field they edited |
| Formulas inside a subform row | As the user types in that row |
| Everything | Once when the form opens, and when a row is added or removed |
So a full-name field fills in when the user taps away from the last name, while a line total inside a row updates keystroke by keystroke.
A Visible when box may read a field that a formula fills in — look a part up, then show follow-up questions based on a property of the part. That works, and the visibility check runs again as soon as the formula writes its result. It follows the formula's timing, not the typing: if the formula sits at the top of the form, the fields it gates appear or disappear when the user leaves the field they edited, not on every keystroke.
Reading who is filling the form
Every box also receives fieldscroll — what the platform knows about the
session, as opposed to what the form has collected. Today it holds
fieldscroll.user, the person filling the form in front of you. Use it to
show a question to some people and not others.
fieldscroll.user.roles— the roles that person holds in the organization they are collecting for, as a list. Check withfieldscroll.user.roles.includes('form_admin').fieldscroll.user.email— their email address, which may be empty.fieldscroll.user.id— their account id.fieldscroll.user.is_owner— true for the workspace owner.
// Visible when: only supervisors see the override question.
return !!fieldscroll.user && fieldscroll.user.roles.includes('org_admin');Because it is grouped, you can also capture a whole set at once instead of naming each value:
// Formula on a hidden field: keep a record of who filled this in.
return JSON.stringify(fieldscroll.user);Three things to keep in mind.
Guard the part you read, like meta. fieldscroll is always there, so
it will not error on its own — but a form can run where there is no fill
session, so write !!fieldscroll.user && before reading through it.
Gate on a role, not on an email.
fieldscroll.user.email === 'dana@example.com' writes one person into the
form definition and stops working the day they change jobs. A role check
survives staff changes.
This is a convenience, not a permission control. A question hidden this way is hidden on screen only. The form definition lists every question and every option to anyone who can read the form, and a value can still be submitted through the API. If you need real per-person restrictions on what can be answered, talk to us rather than building it out of visibility rules.
The same applies to a device with no signal that has never synced: it knows the person, but sees an empty role list until the first sync. Write rules so that an empty list fails toward showing the question, not toward hiding something a field user needs.
Hiding a field without a rule
The field settings panel asks this as one question — When this field shows — with three answers, so the common cases need no rule at all.
- Always visible — everyone filling the form sees it.
- Hidden from field users — never shown, but the field still holds and submits a value. This is what to use for an id or key that feeds a lookup filter or your own system. It replaces writing a Visible when rule that always returns false, and it is steadier: there is no rule to evaluate, so nothing can go wrong and reveal the field. A hidden field also skips required checks.
- Visible when… — shown only when your rule allows it.
Below that sits one checkbox, Also hide while it is empty. It is not a fourth choice, because it works with a rule rather than instead of one: both have to allow the field before it shows. Written for calculated fields that should stay out of the way until they produce something, it is checked after formulas run. Zero and false count as values; blank text and an empty list do not.
The checkbox disappears under Hidden from field users, which already hides the field outright and leaves it nothing to decide.
A starting value
Default value fills a field in when a record is created, and the person filling the form can change it. It is available on text, number, choice, and date fields.
Default formula does the same, but works the value out instead of using a fixed one — once, at creation, and then never again:
// Default formula on a hidden field: who started this record.
return fieldscroll.user.email;That is the difference from a Formula, and it matters. A formula runs
again every time the form opens, so a formula reading
fieldscroll.user ends up recording whoever opened the record most
recently rather than the person who filled it in. A default formula
records the moment of creation and leaves it alone.
If a default formula fails — there is no one filling the form yet, or the expression has a mistake — the field simply starts empty. It never blocks the form.
The field settings panel asks this as one question — Where the value comes from — with four answers: entered by the user, a default value, a default formula, or a formula. Picking one clears the others, so a field can only ever have one source.
A default applies once, at creation, and is never reapplied. Opening an existing draft, a dispatched record, or a submitted record leaves the field exactly as it was, so a default can never overwrite a blank somebody left on purpose. Every new row of a repeating section is seeded the same way the first one was.
Records created through the API do not get defaults, because there is no one filling the form — send the value yourself.
What happens to hidden fields
- The field's value is kept, not erased — if the user typed something before the field hid, that value is still submitted with the record. Plan for this when you read the data later.
- A hidden field skips validation, including required — so a required follow-up question only blocks submission while it is visible. This is what makes the "Other" pattern safe.
- One exception to "kept": a selected dropdown option that becomes hidden is deselected, since the choice is no longer offered.
When something goes wrong
Both boxes fail safe:
- A broken Visible when shows the field. Hiding on error could silently skip a required question, so the platform errs toward showing.
- A broken Formula blanks the field and shows an error message on it, so the problem is visible instead of producing a wrong number.
A formula's output is still checked against the field's own rules — a computed number outside the field's min/max shows a validation error like any typed value would.
Rules that refer back to themselves
A Visible when rule can end up in a circle: field A shows only when B is visible, and B shows only when A is. There is no answer to that question, so the fields in the circle are shown and marked with an error. Every other field in the form works normally, and the app names the fields involved rather than reporting the form as broken.
You cannot publish a form that contains a circle, from the dashboard or through the API. The publish is refused and names the path, so it never reaches a device. Saving a draft that contains one still works, because a form partway through an edit can hold a circle on its way to something sensible.
If a form you already published has a circle in it, the fields in that circle are the ones to fix. Look for a rule that reads the field it is attached to. To show a calculated field only when it has a value, use the Hide when empty checkbox instead — a rule that reads its own field is a circle by construction.
Rules that name a field you deleted
Deleting a field does not delete the rules and formulas that read it. Those rules are left pointing at something that is no longer there.
The app does not quietly treat the missing field as empty. A formula in that state computes nothing and shows the reason on the field: "This formula references "old_total", which does not exist in this form, so the value was not computed." A Visible when rule in that state shows the field and marks it with an error, the same way a circular rule does.
Failing this way is deliberate. Reading the missing field as empty would usually hide the field carrying the rule — and a hidden field is never checked for being required, so a form could go out into the field missing an answer nobody meant to make optional.
Like circles, a dangling reference is a warning while you save a draft and a refusal at publish and at set-ready-for-testing. The message names the field and the reference to fix. The fix is either to point the rule at a field that exists or to remove the rule.
Try it in test mode
The builder's Preview button opens the draft in test mode, and the same engine that runs on a device runs there. Formulas recalculate, fields show and hide, and lookups filter against your real data as you fill the form in — on the same schedule as the table above — so you can check your logic in seconds without leaving the builder. Nothing you enter is saved.
Test mode covers the logic. It cannot stand in for a device on two things: it simulates camera, GPS, and signature captures rather than taking real ones, and it never syncs or submits. For a full end-to-end check — real captures, sync, and a submitted record — publish to a test form and open it in the mobile app. What test mode does and does not cover is described in forms.
Keep it simple
Logic stays predictable when each computed field reads from fields the user types into, or from one other computed field in a straight line (a row total feeding a grand total is fine). Deep webs of computed fields reading other computed fields are hard to reason about — for you and for the next person maintaining the form. If a calculation is getting complicated, that is usually a sign it belongs downstream, in a report template or your own system via webhooks.