← Back to Platform Platform · Scripting

Real scripting.
Real routing.

Python-like - script blocks for the logic a radio button can't model — quota gates, screenouts, computed values, cross-question validation. YAML-authored. Version-controlled. Greppable.

Fieldbase platform · Reference

Where it lives

- script is a flow-node type. It sits alongside - qid nodes in the same YAML file, runs in the engine's evaluation order, and reads and writes the same session state every other node sees.

- qid: QAge
  type: number
  min: 18
  max: 99

- script: |
    if QAge.value >= 65:
        hAgeBucket.set("65+")
    elif QAge.value >= 45:
        hAgeBucket.set("45-64")
    else:
        hAgeBucket.set("18-44")

- qid: Q5
  text: Which brands have you used?
  type: multi
  options: brandList

The same accessors work in scripts, in if: expressions, in piped display text, and in option labels.

Accessors

CallReturns
Qxxx.valueThe answer, or None if unanswered
Qxxx.valuesList of selected codes (multi, grid, rank)
Qxxx.labelsList of selected labels
Qxxx.selected(1)True if code 1 is selected
Qxxx.rank(1)Position of code 1 in a rank question
Qxxx.r1.valueGrid row / loop iteration accessor
hVar.set(expr)Write a hidden variable (becomes a column)
quota("name", code)Quota cell for the named quota
.isFull() / .fill()Quota state. fill() is called automatically on complete
endSurvey("status")Terminate — complete, screenout, quotafull, or custom
goto("qid")Jump to a later node (forward only)
lookup("file", key)Resolve a value from a project lookup file

Routing

Every node accepts an if: expression. Evaluated against session state before the step renders — if it returns falsy, the node is skipped.

- qid: Q5
  text: What draws you back to the brand?
  type: open
  if: QNps.value >= 9

- qid: Q6
  text: What would've made it a 10?
  type: open
  if: QNps.value < 10 and QNps.value >= 7

For routing a script decides, call endSurvey() or goto() inside a - script block. Backward jumps aren't allowed — the engine runs a strict forward graph.

Validation

Per-question validation lives on validate:. Raise a string to show it to the respondent; return silently to pass.

- qid: Q3
  type: gridNumber
  rows: brandList
  min: 0
  max: 100
  validate: |
    if sum(Q3.values) != 100:
        raise "Allocations must total 100%."

- qid: Q4
  type: rank
  options: brandList
  validate: |
    if Q4.rank(1) == Q2.value:
        raise "You already picked that brand at Q2 as your current one."

Piping

Any accessor pipes into display text with {{ }}. Resolved at render time against current session state — including looped questions, hidden variables, and lookup results.

- qid: Q7
  text: You picked {{Q6.label}}. Why?
  type: open

- qid: Q8
  text: Here are your top three — {{Q4.labels[0]}},
        {{Q4.labels[1]}}, and
        {{Q4.labels[2]}}. Which one would you
        actually buy next?
  type: single
  options: Q4.values

Full example

Quota gate, derived index, and a conditional flag — one - script block, inline with the survey:

- script: |
    # Interlock quota check — fail the respondent if their
    # age × gender cell is already full.
    if quota("AgeGender", hAgeGenderCell.value).isFull():
        endSurvey("quotafull")

    # Rolling satisfaction index (used later for screenouts
    # and for a derived segment column in exports).
    hSatisfIndex.set(
        (Q7_1.value + Q7_2.value + Q7_3.value + Q7_4.value) / 4
    )
    if hSatisfIndex.value < 3:
        hLowSatisf.set(1)
→ Related deep-dive
How the quota engine stays correct under concurrent traffic: Why survey quotas fail under concurrent traffic .