---
title: "Logic"
description: "Show blocks conditionally, route between pages, finish early, and pipe earlier answers into copy."
group: "Build forms"
order: 4
type: "concept"
keywords:
  - "visibleIf"
  - "conditions"
  - "page jumps"
  - "early end"
  - "answer piping"
  - "calculated"
  - "repeating groups"
updated: "2026-07-20"
---

Logic decides which blocks and pages a respondent can reach, based on the answers already in the form.

## Conditions

Each condition names a `fieldId`, an operator, and an optional value. Conditions in one `visibleIf` or jump rule are combined with AND.

| Operator | Meaning |
| --- | --- |
| `eq` | Answer equals the value |
| `neq` | Answer does not equal the value |
| `contains` | Text or a multi-value answer contains the value |
| `gt` | Numeric answer is greater than the value |
| `lt` | Numeric answer is less than the value |
| `answered` | A non-empty answer exists |
| `not_answered` | No non-empty answer exists |

## Conditional blocks

In the dashboard, open a field's logic settings and add the conditions that reveal it.

```ts
{
  id: "details",
  kind: "long_text",
  label: "What went wrong?",
  visibleIf: [{ fieldId: "outcome", op: "eq", value: "failed" }],
}
```

Hidden answers are removed from the final response. If someone answers a follow-up and then changes the earlier answer so the follow-up disappears, that stale value isn't submitted.

## Page jumps and early end

A page's `next` rules run from top to bottom. The first match wins and chooses another page ID or the literal `end`. If nothing matches, the form continues linearly.

```ts
{
  id: "triage",
  blocks: [
    {
      id: "kind",
      kind: "select",
      label: "What do you need?",
      options: [
        { id: "support", label: "Support" },
        { id: "thanks", label: "Send thanks" },
      ],
    },
  ],
  next: [
    { when: [{ fieldId: "kind", op: "eq", value: "support" }], to: "support" },
    { when: [{ fieldId: "kind", op: "eq", value: "thanks" }], to: "end" },
  ],
}
```

Skipped pages aren't validated, and their answers are dropped. The client navigator and server validator call the same reachability engine, so both agree on the path.

## Answer piping

In the visual editor, open a later field, heading, or paragraph and choose **Value** beside its text control. Pick an earlier field by its label. Hidden fields are available too, so you can insert values supplied by a URL, a default, or initial data. The editor inserts a readable chip and keeps the stable field ID underneath, so renaming the source field does not break the reference.

In a code-defined schema, write the stable field ID between double braces. For a field with `id: "name"`, use `Hi {{name}}, type in your email.` in a field label or description, or in heading and paragraph text. Choice answers render as their option labels, multiple answers join with commas, and an unanswered field renders as an empty string. Write the surrounding sentence so it still reads well before the answer exists.

## Calculated values in logic

A [calculated field](/docs/fields/calculated) derives a number from other answers, and conditions treat it like any numeric answer: `visibleIf` rules and page jumps can compare it with `gt`, `lt`, or `eq`, and piping tokens can insert it into copy. The value recomputes in the same pass as visibility, so a rule reading a calculated field flips the moment its sources change. While any source is unanswered — or hidden by logic — the calculated field reads as unanswered.

One constraint carries over from jumps on plain answers: a jump decision may only depend on answers already given. A `next` rule conditioned on a calculated field therefore requires every field that calculation (transitively) reads to sit on the jump's page or an earlier one; a rule that reaches forward is dropped during validation and the page falls back to linear flow.

NPS and CSAT scoring remain Insights-side derivations from a field's `insightsMetric`; graded quiz scoring still lives in your app or downstream workflow.

## Repeating groups in logic

A [repeating group](/docs/fields/repeating-group)'s children carry their own, narrower logic surface. A child's `visibleIf` may reference only another field in the *same* group instance, evaluated per instance — hiding a field in Guest 2 never touches Guest 1's copy of the same child. Nothing crosses that boundary in either direction: outer visibility rules, page jumps, calculation operands, and piping tokens can't reference a child field, and a child can't reference anything outside its own group. Both are schema errors, not a silent no-op, the same fail-safe rule the rest of this model follows. Aggregating across instances — a count, a sum of a numeric child — isn't part of the condition model yet; it's a named candidate for later work, not something to approximate with today's operators.

## Troubleshooting

- A rule whose `fieldId` doesn't exactly match a stable field ID never fires.
- A jump target must name an existing page ID or `end`.
- Rule order matters — the first match wins.
- Numeric comparisons need a number, rating, linear-scale, or calculated answer.
- A required field on a skipped page is ignored on purpose.
- A repeating group child's `visibleIf` can only name another field in the same group; crossing that boundary is a schema error, not a silent no-op.

## Related

- [Pages and layout](/docs/pages): Split the form into stable jump targets.
- [Schema and validation](/docs/schema): Validate the same reachable path on the server.
- [Build a quiz](/guides/build-a-quiz): Score submitted answers outside the schema and show a result in your app.
