Uppercase Text in XLSForm: Convert or Enforce
To make a text answer uppercase in XLSForm, add a calculate row with translate(${respondent_name}, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') in the calculation column. ODK has no upper() function and no uppercase keyboard, so you either convert the answer after it is typed with translate(), or reject lowercase letters with a regex() constraint. Every example on this page compiles with pyxform and ODK Validate, the same check ODK Central and KoboToolbox run on upload.
How do I convert a text answer to uppercase?
Keep the text question as it is and add a calculate row that converts it:
| type | name | label | calculation |
|---|---|---|---|
| text | respondent_name | What is the respondent's full name? | |
| calculate | respondent_name_uc | translate(${respondent_name}, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') | |
| note | show_name | Name saved as: ${respondent_name_uc} |
translate(string, fromchars, tochars) replaces each character of the second argument with the character at the same position in the third. So a becomes A, b becomes B, and anything not in the list (digits, spaces, hyphens) stays as it is.
Your export then has both columns: respondent_name as typed and respondent_name_uc in capitals. Analysts merge and match on the second one; the first one stays as evidence of what was entered.
How do I allow only capital letters in a text question?
Put a regex() check in the constraint column. Inside a constraint, . is the answer to this question:
| type | name | label | constraint | constraint_message |
|---|---|---|---|---|
| text | village_name | Village name (in CAPITAL letters) | regex(., '^[A-Z -]+$') | Use capital letters, spaces and hyphens only. |
| text | vehicle_reg | Vehicle registration (2 letters then 4 digits) | regex(., '^[A-Z]{2}[0-9]{4}$') | Two capital letters, then four digits, for example DH1234. |
| text | enumerator_code | Enumerator code | regex(., '^[A-Z0-9]+$') | Capital letters and digits only, no spaces. |
The enumerator cannot move on until the answer matches. In ODK, regex() already tests the whole answer, so the ^ and $ anchors are optional, but they make the rule readable and do no harm. Neither ODK Collect nor KoboToolbox can show an uppercase-only keyboard: there is no appearance for it, and a phone with a physical keyboard could bypass one anyway.
Convert or block? For names and places, convert with translate(). A constraint that rejects "Rahima Begum" makes the enumerator retype a correct answer while the respondent waits. Block only codes with a fixed format, such as a registration number or an ID, where the capitals are part of the format.
How do I remove extra spaces from a text answer?
normalize-space() drops spaces at the start and end and turns every run of spaces inside into one. Put it inside translate() to trim and capitalise in one step:
| type | name | label | calculation |
|---|---|---|---|
| text | head_name | Name of the household head | |
| calculate | head_name_clean | translate(normalize-space(${head_name}), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') | |
| text | national_id | National ID number | |
| calculate | national_id_nospace | translate(${national_id}, ' ', '') | |
| note | show_clean | Household head: ${head_name_clean}. ID: ${national_id_nospace} |
The second calculate removes every space, including the ones in the middle. That works because the third argument of translate() is empty: a character in the second list with no partner in the third is deleted.
How do I compare two text answers ignoring case?
Convert both sides the same way, then compare. This checks that the brand named at the end of a retail interview matches the one named at the start, whatever the capitals or extra spaces:
| type | name | label | constraint | constraint_message |
|---|---|---|---|---|
| text | brand_first | Which brand of tea do you buy most often? | ||
| text | brand_confirm | Please confirm the brand of tea you buy most often | translate(normalize-space(.), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = translate(normalize-space(${brand_first}), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') | This does not match the brand given earlier. Check both answers with the respondent. |
Here the lists are swapped to convert to lowercase. Either direction works for a comparison. If the answer can only be one of a known set of brands, a select_one list with numeric codes is the better fix: it removes spelling differences too, which no amount of case conversion can.
How do I build an ID code from the first letters of a name?
Convert first, then cut with substr(). substr(string, start, end) counts from 0, and the end position is not included, so substr(x, 0, 3) gives the first three characters:
| type | name | label | calculation |
|---|---|---|---|
| text | district | District name | |
| integer | hh_number | Household number in the listing | |
| calculate | district_uc | translate(normalize-space(${district}), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') | |
| calculate | hh_id | concat(substr(${district_uc}, 0, 3), '-', ${hh_number}) | |
| note | show_hh_id | Household ID: ${hh_id} |
A household in "khulna" with listing number 27 gets KHU-27. Without the conversion, "khulna" and "Khulna" would give two different IDs for the same district. For IDs that must be unique across devices, see unique IDs in XLSForm.
What about accented letters and other scripts?
translate() only converts the characters you list. For French or Spanish names, add the accented letters to both lists at the same positions:
| type | name | label | calculation |
|---|---|---|---|
| text | commune | Nom de la commune | |
| calculate | commune_uc | translate(${commune}, 'abcdefghijklmnopqrstuvwxyzéèêàâçôûî', 'ABCDEFGHIJKLMNOPQRSTUVWXYZÉÈÊÀÂÇÔÛÎ') | |
| note | show_commune | Commune: ${commune_uc} |
Count the letters if you edit these lists. If the second list is longer than the third, the extra letters are deleted, not converted, and nothing warns you. Scripts without capital letters, such as Bangla, Hindi or Arabic, pass through translate() unchanged, so the same calculate is safe on a multilingual form. A regex(., '^[A-Z ]+$') constraint is not: it rejects every answer typed in those scripts.
Common uppercase mistakes and how to fix them
These are the messages ODK Validate returns, which is what you see when ODK Central, KoboToolbox or the XLSForm converter rejects a form:
| You wrote | Write instead | Error you will see |
|---|---|---|
upper(${name}) | translate(${name}, 'abc...z', 'ABC...Z') | cannot handle function 'upper' |
UPPER(${name}) | Same as above | cannot handle function 'UPPER' |
translate(${name}, 'abc') | Three arguments: the string, the lowercase list, the uppercase list | cannot handle function 'translate' |
translate(., ...) in the calculation column of the text question itself | A separate calculate row | Cycle detected in form's relevant and calculation logic! |
regex(., [A-Z0-9]+) | regex(., '^[A-Z0-9]+$') | contains invalid constraint expression [regex(., [A-Z0-9]+)] |
| Letter lists of different lengths | Lists of the same length | No error, but some letters disappear |
The fourth row is the one most people try first: a question cannot rewrite its own answer, so the conversion always needs its own row. For other messages, see common XLSForm errors, or paste your form into the free XLSForm validator.
ODK added translate() in 2019. Older Collect versions and self-hosted Enketo installs from early 2021 rejected it. If an enumerator's phone shows an error on the calculate, update ODK Collect or KoboCollect before changing the form.
Does this work the same in ODK, KoboToolbox and SurveyCTO?
ODK and KoboToolbox use the same functions. SurveyCTO has its own:
| Platform | Convert to uppercase | Allow only capitals |
|---|---|---|
| ODK (XLSForm) | translate() in a calculate row | regex() in the constraint column |
| KoboToolbox Formbuilder | Add a Calculate question and type the translate() expression | regex() in the question's Validation Criteria, as a manually entered XLSForm code |
| KoboToolbox (XLSForm upload) | Same as ODK | Same as ODK |
| SurveyCTO | upper(${field}) in a calculate field (lower() for lowercase) | regex() in the constraint column |
upper() is SurveyCTO's own function, so a form that uses it fails in ODK Central and KoboToolbox with cannot handle function 'upper'. When one form has to run on all three, test translate() in SurveyCTO's form tester before you rely on it there.
Why clean text in the form, not after fieldwork
Free text is where duplicates come from: "Khulna", "khulna" and "Khulna " are three districts to a merge in Stata or R. Converting in the form costs one calculate row and means every export, every ID and every comparison already uses the clean value. It also keeps the raw answer next to it, so nothing the enumerator typed is lost. When the list of possible answers is short, the better fix is a choice list, covered in the XLSForm constraint examples and the phone number validation guide for format rules.
You can describe it in plain language instead: "save the village name in capitals without extra spaces, and make the vehicle registration two capital letters then four digits". LooprAI writes the calculate and constraint rows, then compiles the form with pyxform before anything is saved.
Next: XLSForm constraint examples →
Frequently Asked Questions
How do I make text uppercase in XLSForm?
Add a calculate row with translate(${name}, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') in its calculation column. The text question keeps what the enumerator typed and the calculate holds the uppercase copy, which you use in IDs, notes and exports.
Is there an upper() function in ODK or KoboToolbox?
No. ODK Validate rejects upper() with the error cannot handle function 'upper', and KoboToolbox uses the same engine. Use translate() with the lowercase and uppercase alphabets instead. upper() and lower() exist only in SurveyCTO.
How do I allow only capital letters in a text question?
Put regex(., '^[A-Z ]+$') in the constraint column and a constraint message such as Use capital letters only. Add 0-9 inside the brackets to allow digits too. The enumerator cannot continue until the answer matches.
Can I show an uppercase keyboard in ODK Collect or KoboCollect?
No. There is no appearance that switches the keyboard to capitals, unlike the numbers appearance for digits. Convert the answer with translate() in a calculate row, or reject lowercase letters with a regex() constraint.
Why can't I put translate() in the text question's own calculation column?
A question cannot calculate a new value from its own answer. ODK Validate stops the form with Cycle detected in form's relevant and calculation logic. Put the translate() expression in a separate calculate row that references the text question.
How do I remove extra spaces from a text answer in XLSForm?
Use normalize-space(${name}) to drop spaces at the start and end and collapse double spaces inside. To remove every space, use translate(${name}, ' ', ''). Both go in a calculate row, and normalize-space() can sit inside translate() to trim and capitalise at once.
No Credit Card Required
Build the form first. Add hosting when you need it.
Start free with LooprAI, then deploy a managed ODK Central server or run DataSnap checks when your project is ready.
Automation handles the mechanical work.
Researchers decide what the evidence means.