Skip to content
Funcionalidades Preços Documentação Contacto Iniciar sessão Começar
Idioma

XLSForm reference

A lookup reference for building a workbook: what CommonLynk reads from each sheet, every column and key, what is not supported, and where it differs from ODK, KoboToolbox and SurveyCTO.

This page is for the person building an XLSForm workbook to import into CommonLynk. It lists what the platform reads from each sheet, what every column and key does, what is not supported and what happens to it, and where CommonLynk differs from ODK, KoboToolbox and SurveyCTO.

The lists of question types, functions, columns, keys and scoring values on this page are generated from the platform itself, so they describe what it does today. For a walk through building a form, see Building a form and Skip logic and scoring.

What CommonLynk reads from a workbook

A plain ODK, KoboToolbox or SurveyCTO workbook imports as it is. Everything CommonLynk adds is optional, and it is named the way those tools name their own additions, so a CommonLynk workbook still opens in them.

SheetWhat is read
surveyOne row per question, group or repeat.
choicesThe options of every choice list, matched to a question by the list name without regard to case.
settingsThe form title, the form ID, the version and the default language.
commonlynkOptional. CommonLynk’s own form settings: see The commonlynk sheet below.
commonlynk_domainsOptional. The labels of the scoring domains: see Scoring below.
Any other sheetNot read. The GUIDE sheet of the template CommonLynk hands out is one of these.

On the survey sheet, the value of each of these columns is applied to the question. The label, hint and constraint message columns take one column per language, as Building a form describes.

  • appearance
  • calculation
  • choice_filter
  • constraint
  • constraint_message
  • default
  • hint
  • label
  • name
  • read_only
  • relevant
  • repeat_count
  • required
  • type

Every other column with a value on a question row is kept on that question: it is shown on the form’s Design tab, listed on the import page as kept but not applied, and written back when you export the form. Nothing you write is lost on the way through.

A question’s name is its identity: skip logic refers to it, an update matches on it, and every export column is named after it. Write it in lowercase letters, digits and underscores, starting with a letter; capitals are lowered and any other character becomes an underscore. Every row needs a name except a note.

Three routes, three meanings

RouteWhat it does
Forms → Import XLSFormCreates a new draft form, and reports row by row what was imported, what was changed on the way and what was kept.
Import XLSForm, on a form’s Design tabUpdates that form in place. Questions are matched by name: a matched question keeps its answers, a new name is a new question, and a question the workbook leaves out is retired if it holds answers and removed if it does not. Once the form holds responses, a question keeps its type.
Export, on a form’s XLSForm cardWrites the form out as a workbook. Nothing on the platform is changed.

A workbook identical to the one a form was last built from is not applied again unless you tick “Apply this workbook again” on the update: without it, the import says that nothing was changed. Tick it when the platform’s importer has improved and the form should be rebuilt from the same file. Importing the same workbook twice into one activity or project creates nothing the second time.

Question types

Each question type in CommonLynk, the XLSForm types that import as it, and what an export writes for it.

Question typeImported fromWritten on export
Short text. A calculate row becomes a calculated short text, and a hidden row a short text.text
calculate
hidden
text
Long textBuilt in CommonLynk onlytext + appearance multiline
Number. A range question imports as a plain number.integer
decimal
range
integer
Phone numberBuilt in CommonLynk onlytext + appearance numbers
Mobile numberBuilt in CommonLynk onlytext + appearance numbers
Date. A time or datetime question imports as a date, and the time of day is not collected.date
time
datetime
date
Single choice, drawn as a listselect_one LISTselect_one LIST
Multiple choiceselect_multiple LISTselect_multiple LIST
Single choice, drawn as a dropdownselect_one LIST + appearance minimalselect_one LIST + appearance minimal
Likert scale, 1 to 5Built in CommonLynk onlyselect_one cl_likert5 + appearance likert
Yes or no. An acknowledge question imports as one.acknowledgeselect_one cl_yes_no
Rating, 1 to 10 starsBuilt in CommonLynk onlyinteger
Matrix or grid. XLSForm has no grid, so an export writes a note in its place.Built in CommonLynk onlynote
Rankingrank LISTrank LIST
GPS location. A geotrace or geoshape question imports as one.geopoint
geotrace
geoshape
geopoint
Voice noteaudioaudio
Photoimage
photo
image
Videovideovideo
Document or file uploadfilefile
Barcode or QR code scanbarcodebarcode
Signaturesignaturesignature
Note: text shown to the person filling the form, with no answernotenote

A type built in CommonLynk only is exported as the nearest XLSForm spelling. Imported again as a new form, that spelling becomes the type it imports as, not the original: a Likert question becomes a choice question, a phone number a short text. An update of a form that holds responses never changes the type of a question it already has.

Groups and repeats open and close with these types, written with an underscore or a space:

  • begin_group · begin group
  • end_group · end group
  • begin_repeat · begin repeat
  • end_repeat · end repeat

Any other type is skipped and named on the import page, and the row is not kept. That includes these metadata types:

  • start
  • end
  • today
  • deviceid
  • username

CommonLynk records when each response was started and submitted, and who submitted it, without them.

The commonlynk sheet

An optional sheet of two columns, with the headings below, one key per row. A value the platform does not recognise is reported on the import page and left unset for you to choose in the form’s settings; it is never guessed at.

  • setting
  • value
KeyOn importMeaning
title_enAppliedThe form title in English. It wins over the form title on the settings sheet.
title_arAppliedThe form title in Arabic.
title::<language>AppliedThe form title in any other language the platform knows, the language named as on a label column.
form_typeAppliedThe kind of form: one of the form types listed below.
scoringAppliedHow the form is scored: a scoring method or a scoring word. See Scoring. Also read as scoring_method.
pass_thresholdAppliedThe pass mark, as a percentage.
subjectAppliedThe register the form is about: one of the respondent groups listed below. Also read as respondent_type.
publicAppliedyes or no: whether the form may be filled through a public link.
scale_maxChecked, never storedThe most the whole form can score. See Scoring.
scored_question_countChecked, never storedHow many questions you marked as scored. The import reports a different count.
cl_versionReadThe version of these additions the workbook was written for, so an older workbook is read as it was meant. A workbook without it is read as version 1.

These keys are recognised as your own notes. They are listed on the import page and never applied, and any other key you add is treated the same way:

  • domains
  • id
  • indices
  • instance_name
  • items_per_domain
  • note
  • notes
  • purpose
  • purpose_ar
  • purpose_en
  • respondent
  • version

The form types, for the key form_type:

ValueMeaning
registrationRegistration
needs_assessmentNeeds assessment
baseline_assessmentBaseline assessment
endline_assessmentEndline assessment
pre_testTest taken before an activity
post_testTest taken after an activity
coaching_logCoaching session log
satisfaction_evalSatisfaction evaluation
feedbackFeedback form
referralReferral form
otherOther
enterprise_followupAnswered for an establishment some time after its owner's training. The dashboard reads the questions named ef_formalised, ef_formalised_date, ef_hired_since_training and ef_new_staff.

The respondent groups, for the key subject:

ValueMeaning
individualIndividuals
householdHouseholds
establishmentEstablishments

The cl:: question columns

Optional columns on the survey sheet, one value per question. They apply only on a row marked as scored: points or an answer key on a row that is not scored is kept and reported, not applied.

ColumnMeaning
cl::scoredyes to count the question in the score.
cl::pointsA whole number. In a knowledge test, the points a correct answer earns; in a rating score, see How a question adds to a Rating Sum.
cl::correctThe name of the correct option, or the correct text, for a knowledge test. Applies to choice, yes or no and short text questions.
cl::domainThe key of the scoring domain the question counts towards.
cl::reverseyes when a higher answer is better. The question counts in its domain and is reported beside the form score, never added to it or taken from it.
cl::boundOn a note: yes shows the note in the same card as the question after it, read out with that question. Exported, it is an ordinary note carrying this marker, so importing the file again restores it.

Any other column beginning cl:: is kept, listed as kept but not applied, and written back on export, like any column the platform does not interpret.

Scoring

A form is scored by one of four methods. Set it with the scoring key, or in the form’s settings.

ValueWhat it does
noneNo score.
knowledgeA test with an answer key. Each scored question earns its points when the answer matches its correct answer, compared without regard to case. An unanswered question earns nothing out of its points, and a question the form’s logic skipped is left out of the maximum. On a multiple choice question only the first option ticked is compared.
rating_averageThe average of the scored questions’ values, each weighted by its points, which are 1 unless you set them. The percentage reads each value against the top of its own scale.
rating_sumThe total of the scored questions. How each question adds to it is set out below.

The scoring key also takes these words, and each becomes a method:

WordBecomes
itemsknowledge when any scored question has an answer key in cl::correct, otherwise rating_sum.
scalerating_average
pointsrating_sum
calculatedrating_sum
domainsrating_sum

How a question adds to a Rating Sum

A choice question adds the value of the chosen option divided by the highest value among its options, times its points. An option named 0, 1, 2 and so on has that value; an option named yes has the value 1 and one named no the value 0, matched on the name and never on the label. With options 0 to 2 and 6 points, the question adds 0, 3 or 6. When the points equal the highest option, the question adds the plain value: nine questions with options 0 to 3 and 3 points each add up to between 0 and 27.

A typed question, which is a number, or a Likert or Rating question built in CommonLynk, adds its value as typed, times its points. So the same points mean two things: the most a choice question can add, and a multiplier on a typed one.

Every select_one question in a workbook imports as a choice question, whatever its options or its appearance, so in a workbook the choice rule always applies. The typed rule applies to integer and decimal questions, and to Likert and Rating questions built in CommonLynk.

A scored choice question whose options are neither numbers nor yes and no cannot be scored, and neither can a multiple choice question: the import names it, and it adds nothing.

In Rating Sum and Rating Average, a question with no answer, whether skipped or left empty, is left out of both the score and the maximum.

scale_max on the commonlynk sheet is the most the whole form can score. It is a check, not a setting: the import adds up what the scored questions can score and reports a difference. It is not the rating scale maximum in the form’s settings, which is the top of one question’s scale and is used for a typed question with no maximum of its own.

An update that changes how a form scores rescores its complete responses in the same import, and any indicator figure computed from them is marked to be computed again.

Domains and reverse questions

A domain is a label on questions, not a group: two questions in one group may count towards different domains. Name a scored question’s domain in cl::domain. Every complete response then stores a score for each domain beside the form score; it is shown on the response, exported beside the score and written back on export, and an indicator can read one domain instead of the total.

A commonlynk_domains sheet gives each domain its label: one row per domain, with key, a label column per language and, optionally, max, the most the domain can score, which is checked like scale_max and never stored. Without the sheet, a domain is labelled from its group when that group holds only that domain; otherwise the import asks for a label. With the sheet, a cl::domain naming a key it does not list is reported and not applied.

keylabel::English (en)label::Arabic (ar)max
riskRisk factorsعوامل الخطر28
protProtective factorsعوامل الحماية8

A question marked reverse runs the other way: a higher answer is better. It counts only in its own domain, reported beside the form score, and is never added to it or taken from it, so a declared total is never exceeded. If every scored question is reverse, they make up the form score themselves.

In the example above, the risk questions make up the form score out of 28, and the protective questions, marked reverse, are reported beside it out of 8.

Expressions

The relevant, constraint and calculation columns are read by one grammar, and evaluated the same way on the page, on a phone offline and on the server. A function accepted in one column is accepted in the other two.

An expression may use other answers, numbers, quoted text, arithmetic, comparisons, and, or and brackets. In a constraint, a dot stands for the answer being checked. An unanswered question makes a calculation blank, not 0; to count a blank as 0, say so with if().

  • ${name}
  • 'text'
  • + - * div mod
  • = != > >= < <=
  • and or
  • ( )
  • .

These are the functions it evaluates:

FunctionWhat it does
if()if(condition, then, otherwise). The condition may join comparisons with and and or.
concat()Joins its arguments as text.
choice-label()Given a question and a value: the label of that option in the question’s list. The question is named, never read as an answer.
count-selected()How many options a multiple choice answer holds; 0 when it holds none.
false()False.
index()The position of the current repeat entry, counting from 1; blank outside a repeat.
not()Turns true into false and false into true.
pulldata()pulldata(file, column, key column, key): a value from a dataset attached to the form, from the row whose key column equals the key. The first three are written in quotes; no such row, or a blank key, gives a blank.
regex()regex(text, pattern): true when the pattern is found in the text. The pattern is written in quotes.
selected()selected(question, value): true when that option is among the answers. A tick counts 1 in arithmetic.
selected-at()Given a question and a position: the option at that position of a multiple choice answer, counting from 0. With index() it tests the current repeat entry.
string-length()The number of characters in a text.
today()The day the answer is given, in the workspace’s time zone. See Dates below.
true()True.
count()Over a repeat: how many entries hold an answer. Name the repeat itself or any question inside it.
sum()Over a repeat: the total of a question inside it.
min()Over a repeat: the smallest value of a question inside it.
max()Over a repeat: the largest value of a question inside it.
avg()Over a repeat: the average of a question inside it.

In a calculation only, this shape gives the label of the option chosen in a question, as ODK writes it:

  • jr:choice-name(${q}, '${q}')

Reading a repeat as a whole

count(), sum(), min(), max() and avg() read every entry of a repeat from anywhere in the form. Name the repeat itself to count its entries, or a question inside it for its values. Only entries that hold at least one answer count. With no entries, count() and sum() give 0 and the others a blank; one blank or non numeric value makes sum(), min(), max() and avg() blank. The value follows entries as they are added or removed, and is computed again on submission. An aggregate must name a repeat or a question inside one; any other name is reported and stored blank.

To count the entries that match a condition, write a calculation inside the repeat that is 1 for a match and 0 otherwise, and sum() it after the repeat:

typenamecalculationWhere
calculatee_is_femaleif(${e_sex} = 'female', 1, 0)Inside the repeat
calculatejobs_femalesum(${e_is_female})After the repeat
Do not write the condition outside the repeat. There, a comparison with a question inside the repeat is true when any entry matches, so it can say whether there is one but never how many. That is how XPath reads it in every XLSForm tool; CommonLynk refuses it rather than return a wrong count.

Dates

today() is the day the answer is given, in the workspace’s time zone. Two dates written as year, month and day, including a date written in quotes, compare correctly as earlier and later.

Offline, today() is the day the response was collected, not the day it reached the server. A response already submitted is never checked again against a later day. If a phone did not report the day, a constraint that uses today() is not judged: the answer is kept and the question is flagged for review. In the default column, today() is the day the form is opened.

What is not supported, and what happens to it

A fragment the platform does not evaluate is named on the import page, the rest of the row still applies, and your original text is kept on the question and written back when you export the form. Until you change it, what happens depends on the column:

  • in relevant, the question always shows;
  • in constraint, that part is not enforced, though a simple bound in the same constraint still is;
  • in calculation, the value is stored blank.
ExpressionWhy
now()A time of day is not part of the grammar. Use today() for the date.
date('2026-01-01')Not evaluated. A date written in quotes compares as a date without it.
count(${employee}[e_sex = 'female'])A filter in square brackets is not part of the grammar. Count matches with a helper calculation inside the repeat, as above.
sum(if(${e_sex} = 'female', 1, 0))Refused rather than miscounted: see Reading a repeat as a whole.
count-selected-by(${crops}, 'onion')Not an XLSForm or ODK function. It is reported like any unknown function.
today() + 7Arithmetic on a date is not part of the grammar. Compare dates instead, for example . <= today().
round(${area}, 2)Any function not in the table above is reported the same way.

Unsupported structure is reported too: a repeat inside a repeat is merged into the one around it, a relevant on a begin_repeat row is not applied, and appearance minimal on a select_multiple is drawn as a checkbox list. The differences below say why.

Differences from ODK, KoboToolbox and SurveyCTO

Where a workbook that works in those tools behaves differently here, and why. Each is deliberate unless its row says otherwise.

CommonLynkODK, KoboToolbox and SurveyCTOWhy
A repeat entry counts only if it holds at least one answer, in count() and in every other aggregate.Every entry created counts, empty ones included.The count then matches the rows the export contains and the figure an indicator reads. A count of 5 beside four exported rows cannot be defended.
An XPath filter in square brackets is reported, kept as written and not evaluated.Evaluated.One grammar evaluates every rule on the page, offline and on the server, and it has no filters. Conditional counting has a supported form: the helper calculation.
now() date()
Both are reported and not evaluated, and so is date arithmetic.
All three are evaluated.today() is the one date the grammar defines, so that a rule gives the same answer online, offline and on the server.
A repeat inside a repeat is merged into the one around it, and the import says so.Repeats nest.A repeat here is one level deep by design. Merging keeps every question and its answers; nothing is dropped.
A relevant on a begin_repeat row is not applied, and the import says so.The condition shows or hides the whole repeat.Not supported yet. Put the condition on a group around the repeat, or on the questions inside it; both are applied.
appearance minimal on a select_multiple is drawn as a checkbox list, and the import says so.A compact dropdown of checkboxes.A dropdown here holds one answer. Drawing a multiple choice question as one would lose every option but the first.
The metadata types start, end, today, deviceid and username are skipped, and the import names each one.Recorded with every submission.CommonLynk records when each response was started and submitted, and who submitted it, on every response.
A time or datetime question imports as a date.Time is a type of its own.Not supported yet: there is no time question type.
Scoring is part of the form: a method, points per question, domains and reverse questions, stored on every response, exported, rescored when the form changes and readable by an indicator.No scoring model. A score is a calculate row the author writes.A stored score is one figure that every report, export and indicator reads alike. Those tools have no scoring model to follow, so this one is designed from the instruments it scores.
A form with a repeat is refused on the anonymous public link. A respondent’s own Form Lynk renders repeats in full.Web forms render repeats.The public link page renders no repeat, so entries would be lost without a word. Refusing is safer than losing data.

Offline behaviour that changes how you build

  • A constraint is checked on the phone as the answer changes, before the next screen and on submission, offline too, and the server checks it again.
  • today() follows the rules under Dates: offline it is the day the answer was given, never the day the response reached the server.
  • Only the columns a form’s pulldata() reads travel to collecting phones with the form. Attach the dataset before publishing: a form whose dataset is missing cannot be published or set active. Replacing the dataset publishes a new form version, and responses keep the data they were filled with.
  • A question named after a profile field of the form’s respondent group is prefilled from the respondent’s record, and that field then travels to collecting phones for everyone on the list. Rename the question if it does not need prefilling.