Skip to content
ImpactMojo ImpactMojo
Browse Membership
Back to Code Studio
Code Studio · Guided tool course

KoboToolbox and ODK: building survey forms with XLSForm

Write a household survey once, as a spreadsheet, and run it on Android phones with no network: skip logic, range checks, Hindi and English labels, a household roster. KoboToolbox and ODK both read the same XLSForm standard. Python cells on this page check a form for common mistakes and check a downloaded dataset against the form's rules.

KoboToolbox and ODK do not run on this page. You build forms in a spreadsheet, upload them to a server and collect on a phone. The grey boxes are XLSForm sheets for you to type into Excel or Google Sheets, laid out in columns as they would appear there. This page shows no screenshots of either tool, because it cannot produce them. The Python cells check a form definition and a downloaded file, and run here. The first Python run downloads the engine once (about 10 MB).
Module 1 of 7

KoboToolbox, ODK Collect and ODK Central

Most household surveys in South Asian programmes are now collected on Android phones. Two families of free tools dominate, and they are closely related.

What it costs, checked 6 October 2026

Which KoboToolbox server

KoboToolbox runs two public servers with the same features. The account guide (updated 3 October 2026) calls the Global server (kf.kobotoolbox.org) the one "used by most KoboToolbox users"; the glossary says it is hosted in the United States. The European Union server (eu.kobotoolbox.org) is "hosted in Ireland". Projects cannot be moved between them, so choose before you build.

The "humanitarian server". Older manuals and trainers refer to kobo.humanitarianresponse.info, the KoboToolbox server owned by UN OCHA. Kobo announced on 1 September 2023 that it had taken full responsibility for that server, which became the European Union server at eu.kobotoolbox.org. The old addresses forwarded until 29 February 2024. If an old form or phone still points at a humanitarianresponse.info address, change it to the EU server's addresses.
Exercise. Before you open an account, write down three things for your project: whether your organisation is a nonprofit, government agency or university (which decides the free plan); roughly how many submissions a month you expect at peak; and whether your donor or ethics committee says where the data must be stored (which decides the server).
Module 2 of 7

The three sheets of an XLSForm

An XLSForm is an ordinary .xlsx workbook. Each row of the survey sheet is one question; each row of the choices sheet is one answer option; the settings sheet names and versions the form. Formatting, colours and column order are ignored, so you can shade and freeze rows to make the sheet readable.

The survey sheet

Three columns are required: type, name and label. The grey box shows the form this course uses. Each column below is explained in the next module.

XLSForm: survey sheet
sheet: survey
type                   name               label                                              relevant                    constraint              constraint_message              required  choice_filter
select_one state       state              State                                                                                                                               yes
select_one district    district           District                                                                                                                            yes       state=${state}
select_one area        area               Is this household rural or urban?                                                                                                   yes
integer                hh_size            How many people usually live in this household?                                 . >= 1 and . <= 30      Enter a number from 1 to 30     yes
decimal                land_acres         How many acres of land does the household own?    ${area} = 'rural'           . >= 0 and . <= 100     Enter 0 to 100 acres            yes
select_one yes_no      has_bank_account   Does anyone in the household have a bank account?                                                                                   yes
select_one yes_no      received_transfer  Did the household receive a cash transfer in the last 12 months?                                                                    yes
integer                transfer_amount    How much did the household receive in total (Rs)? ${received_transfer} = 'yes'  . > 0               Enter an amount above zero      yes

The choices sheet

Three required columns: list_name, name and label. The word after select_one in the survey sheet must match a list_name exactly. The name column is what is saved in the data, so keep it short, lower case and free of spaces; the reference warns that choice names for select_multiple must not contain spaces, because a space separates the selected answers.

XLSForm: choices sheet
sheet: choices
list_name   name        label       state
state       bihar       Bihar
state       kerala      Kerala
district    gaya        Gaya        bihar
district    purnia      Purnia      bihar
district    kozhikode   Kozhikode   kerala
district    wayanad     Wayanad     kerala
area        rural       Rural
area        urban       Urban
yes_no      yes         Yes
yes_no      no          No

The settings sheet

Optional, but the reference recommends form_title, form_id and version at minimum. A common convention for version is yyyymmddrr: 2026100601 is the first revision of 6 October 2026. Change it every time you change the form, so you can tell which version produced each submission. instance_name builds a readable name for each submission from its answers.

XLSForm: settings sheet
sheet: settings
form_title                      form_id            version       instance_name
Household baseline 2026         hh_baseline_2026   2026100601    concat(${district}, '-', ${hh_size})

Check a form before you upload it

The cell below holds the same survey and choices sheets as CSV text and checks four mistakes that are easy to make by hand: duplicate or invalid names, a select pointing at a list that does not exist, a ${name} that refers to a question not yet asked, and a skip rule comparing a select with a choice name it does not have.

The form has 8 questions, 10 choices in 4 lists, and the check reports no problems.

Exercise. In the survey text, change ${area} = 'rural' to ${area} = 'Rural' and run again. The check reports that area's choices are ['rural', 'urban']. The capital R is the label; the data holds the name. This mistake hides a question from every enumerator and raises no error on the phone. Then change hh_size in the first column of its row to hh size and see what the name check says.
Module 3 of 7

Skip logic, constraints and calculations

These columns are where a form stops entry errors before they reach your data. Every expression refers to an earlier answer as ${name}.

XLSForm: a calculation shown back to the enumerator
sheet: survey
type          name          label                                         calculation
integer       hh_size       How many people usually live here?
integer       monthly_exp   Total household spending last month (Rs)?
calculate     pc_exp                                                      ${monthly_exp} div ${hh_size}
note          pc_note       Spending per person: ${pc_exp} rupees. Is that right?

Showing a derived figure in a note is one of the cheapest checks you can add: an enumerator who sees "Spending per person: 23 rupees" knows something was typed wrong. In ODK expressions, div is division.

Repeat groups: one set of questions per household member

Wrap questions between begin repeat and end repeat rows to ask them once per member, plot or child. repeat_count fixes the number of repeats; the XLSForm reference shows it set from an earlier answer, so the roster opens exactly ${hh_size} times. begin group and end group group questions on one screen without repeating them.

XLSForm: a household roster
sheet: survey (a household roster)
type               name           label                               repeat_count   relevant               constraint
integer            hh_size        How many people usually live here?                                        . >= 1 and . <= 30
begin repeat       member         Household member                    ${hh_size}
text               member_name    First name of this member
integer            age            Age in completed years                                                    . >= 0 and . <= 110
select_one yes_no  in_school      Is this member attending school?                   ${age} >= 5 and ${age} <= 17
end repeat
A roster is a second table. When you download data from a form with a repeat, the members arrive in their own table, linked to the household. KoboToolbox's export guide recommends the XLS format when collecting repeat group data, because the repeat goes to its own sheet. Plan the analysis join before fieldwork.

Run the same rules on the data you download

Constraints catch errors at entry, but not every error: a form version without the constraint, an answer edited on the server, a submission sent twice. The cell applies the form's rules to households.csv (illustrative data, invented for teaching: 240 households in ten real district names, with made-up answers). It then plants five entry errors and one duplicate submission in a practice copy and runs the same check.

The original file has 240 rows and 0 problems. The practice copy has 241 rows and 7 problems: the two out-of-range household sizes, the negative land, the urban household reporting land, the Y that should be Yes, and household 200 listed twice (one row per copy).

Exercise. Add a rule that flags monthly_pc_exp above Rs 15,000 as "check with the enumerator". Write it as "pc_exp above 15000": df["monthly_pc_exp"] > 15000, inside rules and run again. A flag to check is different from an error: a large value can be true. Look at how many households it flags in the original file before you decide on the cut-off.
Module 4 of 7

Hindi and English in one form

One form can carry every language your enumerators use. The XLSForm reference names each language column label::language (code). The ODK form language guide explains: "Each language column adds two colons and the language name, followed by the two letter language code in parentheses", for example label::English (en). For Hindi the code is hi, so the column is label::Hindi (hi); Bengali is bn, Tamil ta, Telugu te, Marathi mr, Urdu ur.

XLSForm: two languages
sheet: survey
type                 name          label::English (en)                               label::Hindi (hi)
integer              hh_size       How many people usually live in this household?   इस परिवार में आमतौर पर कितने लोग रहते हैं?
select_one yes_no    has_bank      Does anyone in the household have a bank account? क्या परिवार में किसी का बैंक खाता है?

sheet: choices
list_name   name   label::English (en)   label::Hindi (hi)
yes_no      yes    Yes                   हाँ
yes_no      no     No                    नहीं

sheet: settings
form_title                form_id            version       default_language
Household baseline 2026   hh_baseline_2026   2026100601    Hindi (hi)

A translation added by hand is easy to leave half-finished: someone adds a question in English during the pilot and forgets the Hindi. The cell finds every row with English text and no Hindi.

It reports one gap: the shg_member question has an English label and no Hindi. The hh_size row passes because both its label and its constraint message are translated.

Exercise. Fill in a Hindi label for shg_member (between the two commas after the English text) and run again. Then rename the column label::Hindi (hi) in the choices text to label::Hindi and see what the check says. Have a fluent speaker who did not write the translation read every label aloud during the pilot; a check like this one finds missing text and cannot judge whether the Hindi is right.
Module 5 of 7

Deploy, collect offline and download

Upload and deploy in KoboToolbox

  1. On the Projects page, select NEW, then Upload an XLSForm, and choose your .xlsx file (from the KoboToolbox XLSForm guide, updated 28 August 2026).
  2. Enter the project details and click Create project.
  3. Click Preview and fill the form yourself, trying wrong answers on purpose to see each constraint message.
  4. Deploy the form. To change it later, open the FORM page, click Replace form, upload the new .xlsx and redeploy. Raise version first.

Set up phones

  1. Install KoboCollect from the Google Play Store. The setup guide (updated 23 April 2026) says newer versions require Android 8.0 or higher; older phones can install the last version that supports them.
  2. Enter the server URL, which differs from the login address: https://kc.kobotoolbox.org/ for the Global server and https://kc-eu.kobotoolbox.org/ for the EU server. The project's FORM tab also shows it under Collect data.
  3. Enter the enumerator's username and password. Once one phone is set up, its QR code configures the rest of the team's phones with the same settings.
  4. Select Download form and tick the form. ODK Collect uses the same menu; it is on Google Play too.

Collecting with no network

Once the blank form is on the phone, no connection is needed to fill it. The ODK Collect guide explains the cycle: a form saved part-way is a draft; tapping Finalize at the end locks it; finalized forms wait in Ready to send until the phone is online, then go to the server. Ask enumerators to sync every evening they have signal, so a lost phone loses one day's work at most.

Download the data

  1. Open the project and go to DATA > Downloads (export guide, updated 6 May 2026).
  2. Choose the type: XLS (recommended when the form has repeats), CSV, SPSS Labels, GeoJSON, GPS coordinates (KML) or media attachments (ZIP).
  3. Choose the value and header format. Labels is the default: question text as headers and choice labels as values. XML values and headers gives the name columns and choice names. Pick XML values for analysis: question text makes poor column names, and labels change between languages.
  4. Click EXPORT, then DOWNLOAD when the file appears in the list.

A daily check by field team

During fieldwork, download every day and compare teams. Large differences between teams working in similar areas are often a training problem. The cell joins households.csv to districts.csv (both invented for teaching) to get each household's field team.

All 240 rows join, with no unmatched districts. Team A covers four districts and 96 interviews; Teams B and C cover three districts and 72 interviews each. Read the three percentages side by side: here the bank account share runs from 84.4% to 88.9% across teams and the transfer share from 33.3% to 41.7%. In real fieldwork, a gap far wider than this deserves a phone call to the supervisor.

Exercise. Change groupby("field_team") to groupby(["field_team", "area"]) and run again. Teams cover different mixes of rural and urban households, and a difference that disappears within area was a difference in where they worked.
Module 6 of 7

Data protection: encryption, access and the DPDP Act

A household survey holds names, phone numbers, locations, caste and income. Three layers protect it: who can see submissions, whether the server can read them, and what the law requires of you.

Who can see submissions

In KoboToolbox, open the project's SETTINGS page and select Sharing. The permissions guide lists separate permissions to view the form, edit the form, view, add, edit, validate and delete submissions, and manage the project. Enumerators need Add submissions only. Permissions can also be limited by row, so a district coordinator sees only submissions from their own enumerators.

Encrypted forms

The XLSForm reference says encryption keeps finalized records private while they are "stored on the device and server as well as during transport", and that encrypted records "are completely inaccessible to anyone not possessing the private key". You make a key pair, put the public key in the form and keep the private key.

Terminal: make a key pair (OpenSSL)
# From the ODK documentation on encrypted forms. Run in a terminal (macOS, Linux)
# or after installing OpenSSL on Windows.
openssl genpkey -out MyPrivateKey.pem -outform PEM -algorithm RSA -pkeyopt rsa_keygen_bits:2048
openssl rsa -in MyPrivateKey.pem -pubout -out MyPublicKey.pem
# Paste the text of MyPublicKey.pem, without the BEGIN and END lines and without line breaks,
# into the public_key column. Keep MyPrivateKey.pem off shared drives and out of email.
XLSForm: settings sheet for an encrypted KoboToolbox form
sheet: settings
form_title                form_id            version      submission_url                             public_key
Household baseline 2026   hh_baseline_2026   2026100602   https://kc.kobotoolbox.org/submission      MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... (your whole key on one line)

The DPDP Act 2023

India's Digital Personal Data Protection Act, 2023 (published 11 August 2023) applies to digital personal data, and a phone survey is digital from the first answer. The organisation that decides why and how the data is processed is the Data Fiduciary; the respondent is the Data Principal. The duties below, the exemption and the penalties apply from 13 May 2027 (notification G.S.R. 843(E), 13 November 2025), so design forms to them now. Sections that matter for a survey:

The research exemption has conditions. The DPDP Rules, 2025 (G.S.R. 846(E), 13 November 2025) set those standards in rule 16 and the Second Schedule: processing must be lawful, limited to the data necessary, made reasonably accurate, kept only as long as needed, protected by reasonable security safeguards, and someone must be accountable for it. Rule 1(4) says rules 3 and 5 to 16 come into force eighteen months after publication. A baseline used to select beneficiaries is a decision specific to a person and falls outside the exemption. Ask your organisation's data protection lead, and see the Data Protection and the DPDP Act deck.

Share a file without identities

Before you share data with an analyst, replace the identifier with a code only the data manager can reverse, and coarsen exact values that could identify a household. The cell does both on households.csv (invented data) with a keyed hash.

The shared file keeps all 240 rows with a 10-character pid in place of hh_id, every pid is unique, and land is reported in four bands. The key table linking pid to hh_id stays with the data manager.

Exercise. Change one character of KEY and run again: every pid changes. That is why the key must be kept, and kept apart from the data. A keyed code makes the file pseudonymous; a village name, a household size and a caste together can still point to one family, so check small groups before you publish anything.
Module 7 of 7

Pilot testing a form

Every form has errors that only show up when a real enumerator asks a real respondent. A pilot finds them while they are still cheap to fix.

  1. Desk test. Fill the form yourself in Preview, once with ordinary answers and once trying to break every constraint. Check that each skip opens and closes when it should.
  2. Phone test. Install the form on the cheapest phone your team will use, switch on airplane mode, fill five forms, then sync. This tests offline storage, the screen size and the Hindi font on that phone.
  3. Field pilot. Interview 15 to 30 households outside the sample, in each language. Note every question respondents ask to have repeated, and every answer that did not fit the choices.
  4. Check the data. Download the pilot submissions as XML values and run your checks (module 3) on them. A constraint that never fires may be too loose; one that fires often may be wrong, or the question may be unclear.
  5. Fix and version. Change the form, raise version, replace the form, delete the pilot submissions or mark them, and record each change and its reason in a change log.

What to look for in pilot data

Exercise. Take the survey sheet from module 2 and add three rows at the top: start, end and a consent question select_one yes_no named consent. Wrap the rest of the form in begin group and end group rows with ${consent} = 'yes' in the group's relevant. Upload it to KoboToolbox and test that answering no ends the interview.

Where next

→

Spreadsheets for M&E

Build indicator tables from the data you downloaded.

→

pandas for development data

Clean, check and summarise survey exports in Python.

→

OpenRefine

Fix district and village names typed five different ways.

→

Survey Design 101

Questions, sampling and piloting before you build the form.

→

Data Protection and the DPDP Act

What the law asks of you when you collect personal data.