# Survey Designer {#survey-designer}


The MQUAD survey designer is a visual, drag-and-drop interface for building surveys. It supports the full XLSForm standard with MQUAD enhancements for constraint expressions, skip logic, and data validation. This page documents the designer in full: the interface, every question type (its configuration in the designer **and** how it behaves when someone fills in the form), choices, appearance, languages, and logic.

> **Before you get here:** a form must be created inside a [project](/user-manual/projects.md) inside a [workspace](/user-manual/workspaces.md). See [Surveys](/user-manual/surveys.md) for the creation flow. After creating a form the designer opens automatically; otherwise open it from the Surveys list via **Design Form**.


## Interface Overview {#interface-overview}


The designer header shows **Form Designer** with the form's title, a last-saved indicator, and an auto-saving indicator. The body is organised as a row of **tabs** across the top, and the **Design** tab is a three-panel workspace.


### Tabs {#tabs}


| Tab | Purpose |
|-----|---------|
| **Design** | Add, arrange, and configure individual questions (the main workspace). |
| **Choices** | Worksheet editor for all choice lists (select_one / select_multiple) in one place. |
| **Preview** | Render the form as a respondent would see it, with live logic. |
| **Settings** | Form-level settings: languages and general options. |
| **Lookup Setup** | Configure lookup relationships between questions (cascading / reference data). |
| **Lookup Data** | Manage the reference data records used by lookups. |


### Design tab — three panels {#design-tab--three-panels}


- **Left Panel (Add Question / Question Bank)** — switch between **Types** and **Bank**:
  - **Types** lists every question type grouped into categories with a count badge. Each entry shows an icon, the type label, and a one-line description. Click a type to append it to the canvas.
  - **Bank** opens the **Question Bank** browser; you can save questions from the canvas to the Question Bank to reuse them across forms, and clicking a saved question inserts it into the current form.
- **Center Panel (Canvas)** — the form being built:
- The canvas header shows the question/group count (e.g. "12 questions (2 groups)") and a **Form Title** input.
- An empty form shows a **Start Building Your Form** prompt with an **Add First Question** shortcut.
- Questions can be **dragged to reorder**, moved **up/down**, **duplicated**, **deleted**, grouped, and **collapsed** (groups/repeats).
- **Duplicate a question:** Click the duplicate icon on the question card (or use the card context menu) to create a copy of the selected question with its configuration.
- **Multi-select** is available for bulk actions: hold Ctrl/⌘ and click, or use the multi-select mode toggle. A press-and-hold handle on each card is the drag target.
- **Right Panel (Properties)** — configure the currently selected question. Until a question is selected it shows "Select a question to edit its properties".


### Question cards {#question-cards}


Each question on the canvas shows:

- A **type badge** — the question type, or **📅 Calendar Grid**, **🔁 Repeat**, or **📁 Group** for containers.
- The **variable name** (monospace) used in expressions.
- A **Required** badge when the question is mandatory.
- A **Count** badge on repeats that have a fixed count.
- A **months** badge on calendar grids showing the configured window.
- An **Error** badge when the form validator flags the question (e.g. a broken reference). The card border also turns red.


## Question Types Reference {#question-types-reference}


The palette groups types into families: **Text**, **Number**, **Date/Time**, **Choice**, **Location**, **Media**, **Display/Calculated**, **Metadata**, **External Data**, and **Grouping** containers, plus MQUAD's **Calendar Grid**.

The **Runtime input** column below describes how the field appears and behaves when someone fills in the form (identical on the web form and the mobile field app — the same expression engine and rendering rules run on both).


### Text {#text}


| Type | Variable example | Designer configuration | Runtime input |
|------|------------------|------------------------|---------------|
| **Text** | `full_name` | Label, hint, name, required, appearance (`multiline`, `numbers`, `phone`, `url`, `email`), validation, skip logic, auto-populate. | A single-line text box. With `multiline` it becomes a text area; `numbers` renders a numeric box; `phone`/`url`/`email` render validated inputs (tel/url/email keyboards on mobile). |


### Number {#number}


| Type | Variable example | Designer configuration | Runtime input |
|------|------------------|------------------------|---------------|
| **Integer** | `age` | Label, hint, numeric constraints (min/max/range), required, skip logic, auto-populate. | Whole-number entry; rejects non-numeric input. |
| **Decimal** | `weight_kg` | Same as integer, for fractional values. | Decimal-number entry (accepts a decimal point). |
| **Range** | `satisfaction` | Min, max, and step. | A slider with the current value displayed. |


### Date & Time {#date--time}


| Type | Variable example | Designer configuration | Runtime input |
|------|------------------|------------------------|---------------|
| **Date** | `dob` | Constraints (e.g. not in the future / if within last N days), required, skip logic, auto-populate, default value. | A date picker; out-of-range years are bounded and out-of-range selections are rejected with a message. |
| **Time** | `visit_time` | Constraints, required. | A time picker. |
| **Date & Time** | `submitted_at` | Constraints, required. | A combined date + time picker. |


### Choice {#choice}


| Type | Variable example | Designer configuration | Runtime input |
|------|------------------|------------------------|---------------|
| **Select One** | `gender` | Choices list, appearance (`minimal`, `columns-N`, `no-columns`), `choice_filter`, per-choice constraints, required. | Radio buttons or a dropdown. Options can be filtered by `choice_filter` and per-choice constraints; hidden choices are excluded entirely. |
| **Select Multiple** | `symptoms` | Choices list, appearance columns, count limits, `choice_filter`, per-choice constraints. | Checkbox list. Hidden choices are removed; selecting one applies any count limit. |
| **Rank** | `priorities` | Choices list; respondents order them. | A drag-to-reorder list; the resulting order becomes the answer. |
| **Boolean** | `consent_given` | Label, required. | A yes/no choice. |
| **Radio** | `plan_type` | Choices list; rendered as an inline radio group. | Radio group (compact display). |


### Location {#location}


| Type | Variable example | Designer configuration | Runtime input |
|------|------------------|------------------------|---------------|
| **Geopoint** | `household_location` | Label, required, optional accuracy guidance. | Map preview with a marker plus a "capture current location" / pick-on-map control (GPS on mobile). |
| **Geotrace** | `route` | Multi-point capture. | Sequential point capture, drawn as a polyline on the map. |
| **Geoshape** | `plot_boundary` | Multi-point capture. | Sequential point capture, drawn as a polygon on the map. |


### Media {#media}


| Type | Variable example | Designer configuration | Runtime input |
|------|------------------|------------------------|---------------|
| **Image** | `photo` | Optional media constraints (max size, allowed types) resolved from policy; parapdata/notes. | Opens a capture/gallery flow (camera or file, plus the media library). Files are validated against the upload policy and compressed before upload. |
| **Audio** | `voice_note` | Media constraints. | Records audio (or picks a file); validated and compressed. |
| **Video** | `clip` | Media constraints. | Records or picks a video; validated against size/type policy. |
| **File** | `document` | Media constraints. | File picker; validated against allowed types and size. |
| **Barcode** | `product_code` | Label, required. | Camera-based barcode/QR scanner; the scanned value becomes the answer. |
| **Signature** | `signature` | Label, required. | A signature pad; the drawn signature is captured as an image. |


### Display / Calculated {#display--calculated}


| Type | Variable example | Designer configuration | Runtime input |
|------|------------------|------------------------|---------------|
| **Note** | `instructions` | Label and hint only (read-only). | Display-only text; nothing is entered. Commonly used for instructions or section intros. |
| **Acknowledge** | `read_consent` | Label; the respondent confirms. | A checkbox acknowledgement the respondent must tick. |
| **Calculate** | `total_score` | A **Calculated Value** expression (see below). | Not editable; the computed value is stored with the response and can be referenced by later questions. |
| **Hidden** | `internal_id` | Auto-populate / default value. | Never shown; stores a value (e.g. a generated ID). |
| **Trigger** | `refresh_flag` | Trigger expression. | Not shown; fires logic when its condition changes. |
| **Calendar Grid** | `attendance` | `mode` (weekly/monthly), **Months/Weeks back**, start from today, allow future (see below). | A grid the respondent completes; the default window is the last 12 months (weekly) or 12 months (monthly). |


### Metadata {#metadata}


These are auto-populated by the system and generally hidden from the respondent: **start**, **end**, **today**, **deviceid**, **username**, **email**, **phonenumber**, **simserial**, **subscriberid**, and **audit**. They are useful for auditing and for expressions (e.g. referencing `today()`), and record device/time context automatically.


### External Data {#external-data}


| Type | Purpose |
|------|---------|
| **XML External** | Loads an external XML dataset so expressions (e.g. `pulldata()`) can resolve values from it. |
| **CSV External** | Same, for a CSV dataset. |


### Grouping Containers {#grouping-containers}


| Type | Purpose |
|------|---------|
| **Group (begin_group)** | Wraps questions into a section. Groups nest and can be collapsed on the canvas. |
| **Repeat (begin_repeat)** | Repeats a set of questions. A repeat may have a fixed **count** or be driven by logic. Nested repeats render their child questions in the properties panel. |

> **Containers and repeats:** group and repeat boundaries (`begin_group`/`end_group`, `begin_repeat`/`end_repeat`) are managed by the canvas. When you add a container you can add child questions directly into it via **Add to group**, and **ungroup** to lift its children back out.


## Question Properties Panel {#question-properties-panel}


Selecting a question in the canvas fills the right-hand **Properties** panel. The sections are:


### Basic information {#basic-information}


| Property | What it does |
|----------|--------------|
| **Name** | The unique variable name used in expressions (`${name}`) and exports. |
| **Label** | The question text shown to respondents. |
| **Hint** | Optional secondary text shown under the label. |
| **Translations** | Per-language label/hint values when the form is multilingual (see [Languages](#languages)). |
| **Question Type** | Reassign the type (e.g. switch Text → Integer). Rules and options are preserved where compatible. |


### Options {#options}


| Option | What it does |
|--------|--------------|
| **Required** | The respondent must answer before the form can be submitted. |
| **Read Only** | The value is shown but cannot be edited (typically used with **Auto-Populate**). |
| **Repeat count** | For repeat groups fixed to a specific number of repetitions. |


### Choices (choice-based questions) {#choices-choice-based-questions}


Select-one, select-multiple, and rank questions show a **choices editor** where you add options. Each choice has a **name** (value), a **label**, optional per-language labels, optional media, an optional **per-choice constraint** (visibility rule), and lookup/extra columns. Shared lists are managed from the [Choices tab](#choices-tab).


### Advanced {#advanced}


| Action | What it does |
|--------|--------------|
| **Conditional Logic (Skip Logic)** | Opens the skip-logic builder to control when the question is visible. See [Skip Logic](#skip-logic-relevance). |
| **Validation** | Opens the validation builder for constraints. See [Validation Rules](#validation-rules). |
| **Calculated Value** | For **calculate** questions — a read-only computed expression. |
| **Auto-Populate** | For any other type — re-evaluated whenever a dependency changes, and editable by the respondent. |
| **Default Value** | A value set once at form start (unlike auto-populate it is not continuously recalculated). See [Default Values](/user-manual/default-values.md). |
| **Appearance** | Space-separated tokens that change rendering (see [Appearance Tokens](#appearance-tokens)). |
| **Choice Filter** | Filters a select list by the values of extra choice columns (cascading selects). See [Choice Constraints](#choice-constraints). |


### Extended properties {#extended-properties}


Beyond the core fields, the properties panel exposes MQUAD extensions:

- **Choice relation** — bind a question to a shared choice list / relationship.
- **Limit** — restrict how many options can be selected (select_multiple) or the count of a repeat.
- **Paradata** — enable paradata capture (e.g. audio/timing) for a question.
- **Media file** — attach or reference a media asset from the media library.
- **Data privacy** — mark a field as sensitive.
- **Additional values** — extra columns/attributes carried on the question.


### Calendar Grid configuration {#calendar-grid-configuration}


The **Calendar Grid** type has a dedicated configuration:

| Setting | Options | Notes |
|---------|---------|-------|
| **Mode** | Weekly / Monthly | The label of the next setting switches between **Weeks Back** and **Months Back**. |
| **Weeks/Months Back** | Weekly: 1–520; Monthly: 1–120 | How far back the grid extends. Default 12. Values are clamped to the allowed range. |
| **Start from** | Today | The grid's anchor date. |
| **Allow future** | On / Off | Whether cells after the anchor (today) can be filled. |


## Appearance Tokens {#appearance-tokens}


The **Appearance** field accepts a space-separated list of tokens (XLSForm-style), for example `minimal columns-3` or `no-columns`. The designer stores appearance as free text; the runtime interprets recognised tokens:

| Token | Effect | Typical types |
|-------|--------|---------------|
| `multiline` | Render a multi-line text area. | Text |
| `numbers` | Render a numeric keypad/entry. | Text |
| `phone`, `url`, `email` | Render a validated input with the matching mobile keyboard. | Text |
| `minimal` | Compact layout (e.g. inline choice list). | Select one/multiple |
| `columns-N` | Lay choices out in N columns (e.g. `columns-3`). | Select one/multiple |
| `no-columns` | Force a single column. | Select one/multiple |

Unknown tokens are ignored. Keep appearance tokens lowercase and space-separated.


## Choices Tab {#choices-tab}


The **Choices** tab uses a worksheet editor to manage every choice list in the form in one place: add/remove choices, edit names and labels, set per-choice constraints, and manage extra columns. Changes here stay in sync with the per-question choices editor in the Properties panel.


## Preview Tab {#preview-tab}


The **Preview** tab renders the form as a respondent sees it, with live skip logic, validation, and calculated values. Preview submissions do **not** affect project data. Use it to:

1. Verify skip logic reveals and hides questions correctly.
2. Test validation rules with valid and invalid inputs.
3. Confirm calculated and auto-populated values update as expected.
4. Check translations by switching the active language.


## Settings Tab {#settings-tab}


The **Settings** tab manages form-level configuration.


### Languages {#languages}


- **Default language** — the language used when no translation is chosen.
- **Available languages** — the list of languages the form is translated into. Languages can be reordered (drag to reorder).
- **Add language** — opens a dialog to choose a language (code + name, with a flag). After adding, you can provide per-language labels for each question.
- Languages can also be **seeded** from a predefined set and **deleted**.


### General settings {#general-settings}


- **Version & history** — shows the current form version and opens the version history dialog. See [Surveys](/user-manual/surveys.md) for version management.
- **Instance name template** — the pattern used to name submitted instances.
- **Auto-save** — toggles automatic saving and its interval. When enabled, edits are saved periodically and the header shows an auto-saving indicator.


## Lookup Setup and Lookup Data Tabs {#lookup-setup-and-lookup-data-tabs}


- **Lookup Setup** configures **relationships** between questions — for example, cascading selects where one field's options depend on another, or fields that reference external datasets.
- **Lookup Data** manages the records behind those lookups (the reference data the form resolves against at fill time).


## Skip Logic (Relevance) {#skip-logic-relevance}


Skip logic controls when a question is visible. Questions with unmet relevance conditions are hidden automatically.


### Visual Builder {#visual-builder}


1. Select a question and click **Conditional Logic** in the right panel.
2. The **Visual Builder** tab lets you add conditions without writing code.
3. Each condition compares another question's answer to a value:
   - `If age >= 18` — Show this question only when `age` is 18 or older.
   - `If has_children = Yes` — Show only when `has_children` is "Yes".
4. Combine multiple conditions with **AND** (all must match) or **OR** (any must match).
5. Nest condition groups for complex logic like: `(A OR B) AND C`.


### Raw Expression Mode {#raw-expression-mode}


Switch to the **Raw Expression** tab to type XPath expressions directly. The client-friendly format uses `&` for AND and `|` for OR:

```
age >= 18 & has_children = Yes
```

This is automatically converted to the internal XPath format:
```
${age} >= 18 and ${has_children} = 'Yes'
```

If you paste an existing XLSForm expression, the system auto-corrects common syntax issues including bare `&` to `and`, bare `|` to `or`, and missing `.` prefix.


## Validation Rules {#validation-rules}


Validation rules ensure data quality by constraining what answers are accepted.


### Standard Rule Types {#standard-rule-types}


| Rule | Description | Example |
|------|-------------|---------|
| Required | Answer must be provided | Marks question as mandatory |
| Minimum Length | Text must be at least N characters | `string-length(.) >= 3` |
| Maximum Length | Text must be at most N characters | `string-length(.) <= 100` |
| Minimum Value | Numeric value must be >= N | `. >= 18` |
| Maximum Value | Numeric value must be <= N | `. <= 120` |
| Pattern (Regex) | Value must match a regex pattern | `regex(., '^[0-9]+$')` |
| Custom Expression | Any valid XPath expression | `. >= ${min_age}` |


### MQUAD Regex Shortcuts {#mquad-regex-shortcuts}


For common validation patterns, use the built-in shortcuts instead of writing regex from scratch:

| Shortcut | Validates | Example valid input |
|----------|-----------|-------------------|
| `Email Format` | Email address format | `user@example.com` |
| `10-Digit Mobile` | Exactly 10 digits | `9876543210` |
| `Numbers Only` | Digits only | `12345` |
| `Money (1 decimal)` | Number with at most 1 decimal | `100.5` |
| `Letters Only` | Alphabetic characters | `Hello` |
| `Letters & Numbers` | Alphanumeric only | `Hello123` |

When selected, these shortcuts expand to the full regex expression automatically. They work identically whether set via the visual builder or imported from an XLSForm file.


## Calculated Values & Auto-Populate {#calculated-values--auto-populate}


Calculations let you automatically compute field values based on answers to other questions. There are two modes:


### Calculated Value (type = calculate) {#calculated-value-type--calculate}


Use a **Calculated Value** question for read-only computed results. These display as formatted text and cannot be edited by respondents.

Examples:
- Total score: `${q1} + ${q2} + ${q3}`
- Age from DOB: `int(days-between(${dob}, today()) div 365)`
- Composite ID: `concat(${site_code}, '-', ${participant_num})`


### Auto-Populate (any other question type) {#auto-populate-any-other-question-type}


Use **Auto-Populate** on any question type (text, integer, select_one, date, etc.) to pre-fill a value that the respondent can override. Unlike **Default Values** (set once at form start), auto-populate expressions are re-evaluated whenever any dependency changes.

Examples:
- Estimated total on an integer field: `${qty} * ${unit_price}`
- Pre-filled location on a text field: `concat(${city}, ', ', ${country})`


### Setting Up in the Designer {#setting-up-in-the-designer}


1. Select a question in the canvas.
2. In the right panel, scroll to the **Advanced** section.
3. Click **Calculated Value** (for calculate type) or **Auto-Populate** (for all other types).
4. Write your expression using `${question_name}` references and built-in functions.
5. The editor validates in real time — unknown variables and unbalanced parentheses are flagged before saving.

The editor provides a **Variables panel** to insert references and a **Functions panel** with built-in operators like `sum`, `if`, `concat`, `round`, and `days-between`.

For a complete reference of all supported functions and expression syntax, see [Expression Reference](/user-manual/expression-reference.md), [Auto-Populate and Calculations](/user-manual/calculation-expressions.md), and [Date and Time Reference](/user-manual/date-time-reference.md).


### Computed Constraints {#computed-constraints}


Constraints can reference other questions in the survey. For example, a "Total household members" question could have:

```
. >= ${total_members} 
```

This ensures the current answer is at least as large as the total household members reported earlier.


## Choice Constraints {#choice-constraints}


Choice constraints control which options in a select list are visible based on answers to other questions. Each choice in your XLSForm choices sheet can have its own constraint expression. Choices whose constraints evaluate to false are hidden from the respondent.


### When to Use {#when-to-use}


| Use Case | Example |
|----------|---------|
| Score-based visibility | Show a "High" score option only when `score >= 26` |
| Conditional options | Show "Minor" only when `age < 18` |
| Cascading filters | Hide options based on multiple previous answers |
| Dynamic choice lists | Replace complex choice_filter logic with per-choice rules |


### Syntax in the Choices Sheet {#syntax-in-the-choices-sheet}


Add a `constraint` column to your XLSForm choices sheet. Write expressions using question names as variable references:

| list_name | name | label | constraint |
|-----------|------|-------|-----------|
| score_level | high | High | `score >= 26 and score <= 29` |
| score_level | medium | Medium | `score >= 11 and score <= 25` |
| score_level | low | Low | `score < 11` |

The constraint `score >= 26 and score <= 29` means: "Only show this choice when the answer to question `score` is between 26 and 29."


### Supported Expression Patterns {#supported-expression-patterns}


All constraint expressions use the same syntax as survey constraints:

| Pattern | Example | Behavior |
|---------|---------|----------|
| Simple comparison | `score < 5` | Show choice when `score` is less than 5 |
| Range | `score >= 5 and score <= 10` | Show when score is 5 to 10 inclusive |
| OR conditions | `q21 = 1 or q21 = 2 or q21 = 3` | Show when q21 matches any listed value |
| AND conditions | `q21 = 1 and q22 = 2 and q23 = 3` | Show when all conditions are true |
| Mixed conditions | `(q21 = 1 or q21 = 2) and score > 0` | Show when OR group AND extra condition |
| Arithmetic | `q306a + q306b >= 1` | Show when sum of two questions is at least 1 |
| Cross-field reference | `${status} = 'active'` | Show when another question has a specific value |

Bare `&` in expressions (e.g., `score>=5&score<=10`) is automatically converted to `and` during import.


### How Choice Constraints Differ from choice_filter {#how-choice-constraints-differ-from-choice_filter}


| | choice_filter | Per-choice constraint |
|---|---|---|
| **Where defined** | Survey sheet (question level) | Choices sheet (per-choice) |
| **Scope** | Filters the entire choice list | Controls individual choices |
| **Expression context** | Choice's extra columns vs question answers | Question answers only |
| **Use case** | Cascading selects based on external data | Dynamic visibility based on survey logic |

Both can be used together: `choice_filter` runs first to filter the list by extra column values, then each choice's `constraint` is evaluated individually. A choice is only shown when both pass.


### Auto-Correction {#auto-correction}


When importing from XLSForm, the system automatically:
1. **Normalizes operators** — `&` to `and`, `|` to `or`
2. **Expands regex shortcuts** — `regex=email` to `regex(., 'pattern')`
3. **Validates references** — All `${name}` references are checked against existing questions


### Runtime Behavior {#runtime-behavior}


- Choice constraints are evaluated on every answer change.
- Hidden choices are excluded from the selection list entirely — respondents cannot see or select them.
- If a previously selected choice becomes hidden due to a constraint change, the selection is cleared and the respondent must pick a visible option.
- The same constraint engine runs on both web forms and the mobile field app, ensuring identical behavior.


## Expression Reference {#expression-reference}


Constraint and relevance expressions use a subset of XPath with ODK extensions. Both the web form engine and the mobile field app evaluate expressions identically.


### Comparison Operators {#comparison-operators}


| Operator | Meaning | Example |
|----------|---------|---------|
| `=`, `==` | Equals | `. = 5` |
| `!=` | Not equals | `. != 0` |
| `>` | Greater than | `. > 18` |
| `<` | Less than | `. < 100` |
| `>=` | Greater than or equal | `. >= 5` |
| `<=` | Less than or equal | `. <= 10` |


### Logical Operators {#logical-operators}


| Operator | Meaning | Example |
|----------|---------|---------|
| `and` | Logical AND | `. >= 18 and . <= 99` |
| `or` | Logical OR | `. = 0 or . = 1` |
| `not` | Logical NOT | `not(. = 0)` |


### Field References {#field-references}


Reference any question's value using `${question_name}`:

```xpath
${age} >= 18 and ${consent} = 'Yes'
```

The dot `.` represents the current question's value:

```xpath
. >= 5 and . <= 10
```


### Built-in Functions {#built-in-functions}


| Function | Purpose | Example |
|----------|---------|---------|
| `selected(value, choice)` | True if choice is selected (multi-select) | `selected(${q}, 'red')` |
| `count-selected(value)` | Number of selected options | `count-selected(${q}) > 1` |
| `selected-at(value, index)` | Choice at position (0-based) | `selected-at(${q}, 0)` |
| `string-length(value)` | Length of string | `string-length(.) >= 3` |
| `contains(value, substr)` | String contains substring | `contains(., 'urgent')` |
| `starts-with(value, prefix)` | String starts with | `starts-with(., 'A')` |
| `regex(value, pattern)` | Match regex pattern | `regex(., '^[0-9]+$')` |
| `today()` | Current date | `. <= today()` |
| `now()` | Current date and time | |
| `if(cond, then, else)` | Conditional | `if(${age} > 17, . <= 4, . >= 0)` |
| `days-between(a, b)` | Days between dates | `days-between(today(), ${dob}) > 365` |
| `today-offset(days)` | Date offset from today | `. <= today-offset(-10)` |
| `int(value)` | Convert to integer | `int(.)` |
| `number(value)` | Convert to number | |
| `round(value, n)` | Round to N decimals | `round(., 2)` |
| `coalesce(a, b, ...)` | First non-empty value | |
| `uuid()` | Generate UUID | |
| `true()`, `false()` | Boolean constants | |
| `boolean(value)` | Convert to boolean | |


### Common Constraint Patterns {#common-constraint-patterns}


| Pattern | Expression |
|---------|-----------|
| Number between range | `. >= 18 and . <= 99` |
| Minimum age check | `date-diff('days', ${dob}, today()) >= 6570` |
| Conditional validation | `if(${has_children} = 'Yes', . >= 1, . = 0)` |
| Date not in future | `. <= today()` |
| Date within last 10 days | `. >= today-offset(-10)` |
| Multi-select with limit | `selected(${q}, 'Other') and count-selected(${q}) > 1` |
| Cross-field comparison | `. <= ${max_allowed}` |
| Required with condition | `if(${type} = 'other', . != '', .)` |


## XLSForm Import {#xlsform-import}


When importing an XLSForm file, the system automatically:

1. **Expands regex shortcuts** — `regex=email` becomes the full regex expression automatically.
2. **Corrects common syntax** — Bare `&` becomes `and`, bare `|` becomes `or`, bare `>=10` gains the `.` prefix.
3. **Validates field references** — Any `${name}` references are checked against existing questions.
4. **Detects circular dependencies** — Chains like `Q1 → Q3 → Q4 → Q1` are flagged and blocked.


## Best Practices {#best-practices}


- **Create the hierarchy first** — a form needs a workspace and a project before you can design it.
- Use the **Visual Builder** for simple skip logic and validation rules.
- Use **Raw Expression** mode or **Custom Expression** for complex cross-field constraints.
- Use **translation labels** rather than duplicating questions for each language.
- Always **Preview** your survey to verify logic behaves as expected.
- For mobile field data collection, test on an actual device before deploying — media capture, GPS, and barcode behave differently on-device.
- Keep constraint expressions simple — combine multiple simple rules with AND/OR rather than writing one complex expression.
