Authoring forms

A form is written once, by the people who own it, in whichever format suits them:

  • The designer: an outline, a live preview and settings in plain words. No syntax at all. See The designer.
  • Excel: one row per question, in the layout used by XLSForm (known from ODK and KoboToolbox). Office staff can work in a tool they already know, and changes are easy to review.
  • YAML: for developers, version control and code review.

All three compile to the same canonical JSON, which is what runtimes read. Nobody edits the JSON by hand. The designer opens and saves Excel and YAML, so people can move between tools freely.

Structure

A form has pages, and pages have questions. Groups collect related questions. A group also becomes a level in the submitted data: applicant + name is applicant.name.

pages:
  - name: about_you
    label: { da: Om dig, en: About you }
    fields:
      - name: applicant
        type: group
        fields:
          - { name: name, type: text, label: { da: Navn, en: Name }, required: yes }

A form without pages can list its fields directly; it then has one page.

Question types

Type People answer with Submitted as
text a line of text text
integer, decimal a number, typed the local way (1.234,50 in Danish) a number
date a date YYYY-MM-DD
boolean a checkbox true, or nothing
select_one, select_multiple radios or a dropdown, or checkboxes the chosen value(s)
search type, then choose a suggestion (e.g. an address). See Lookups. the chosen value
note nothing: information text —
calculate nothing: a hidden calculated value the result

Rules

Every rule is a formula, written like in Excel. See Formulas for the full language.

Setting Meaning Example
relevant Show the question only when this is TRUE employment = "self"
required Must be answered (yes, or a formula) yes, married = "yes"
constraint The answer is valid only when this is TRUE; . is the answer AGE(.) >= 18
readonly Cannot be changed yes
calculate The value is computed ROUND(@valuation.value * 0.8, 0)
default A pre-filled answer 20

Hidden questions are left out of calculations and of the submitted data, so an answer someone gave before changing their mind cannot slip through.

Messages are yours to write, per language: requiredMessage and constraintMessage. Without them, people see a standard message such as "Feltet skal udfyldes".

Options

Options are shared lists, or come from a lookup:

choices:
  employment:
    - { value: employed, label: { da: Lønmodtager, en: Employed } }
    - { value: self, label: { da: Selvstændig, en: Self-employed } }

fields:
  - { name: employment, type: select_one, choices: employment, label: … }
  - { name: property, type: select_one, choices: "@properties", choiceValue: id, choiceLabel: address, label: … }

Up to six options show as radios, and more as a dropdown.

Text

Every text can be given per language and can include answers or lookup results:

label:
  da: "Forventet månedlig ydelse: {{ loan.monthly_payment | number: 2 }} kr."
  en: "Expected monthly payment: DKK {{ loan.monthly_payment | number: 2 }}"

See Text templates.

The same form in Excel

Sheet Columns
settings id, version, title::da, title::en, languages
survey type, name, label::da, label::en, hint::…, required, required_message::…, relevant, readonly, constraint, constraint_message::…, calculation, default, choice_value, choice_label, verify
choices list_name, value, label::da, label::en
datasources id, param, value, when, query

begin page / end page and begin group / end group rows give the structure. A select question is written as select_one employment or select_one @properties. Formulas may start with =, as in Excel, and Danish Excel's ; between arguments works too. Extra columns, such as your own comments, are ignored.

Checking a form

npx quillflow compile form.xlsx -o form.json

Problems are reported where the author will look for them:

form.xlsx, sheet "survey", row 14, column "relevant": Unknown field "incme". Did you mean "income"?
form.yaml:23: These rules depend on each other in a circle: whether "a" is shown → the value of "b" → …

The designer shows the same messages next to the setting they are about.