SurveyLoopr
Tips & Tricks
10 min read

Uppercase Text in XLSForm: Convert or Enforce

Mehrab Ali
Mehrab AliPublished November 13, 2026

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.

Download example form (.xlsx)All six examples in one XLSForm, one group each. Tested with pyxform and ODK Validate; free to reuse under CC BY 4.0.Download

How do I convert a text answer to uppercase?

Keep the text question as it is and add a calculate row that converts it:

typenamelabelcalculation
textrespondent_nameWhat is the respondent's full name?
calculaterespondent_name_uctranslate(${respondent_name}, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
noteshow_nameName saved as: ${respondent_name_uc}
surveychoicessettings

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:

typenamelabelconstraintconstraint_message
textvillage_nameVillage name (in CAPITAL letters)regex(., '^[A-Z -]+$')Use capital letters, spaces and hyphens only.
textvehicle_regVehicle registration (2 letters then 4 digits)regex(., '^[A-Z]{2}[0-9]{4}$')Two capital letters, then four digits, for example DH1234.
textenumerator_codeEnumerator coderegex(., '^[A-Z0-9]+$')Capital letters and digits only, no spaces.
surveychoicessettings

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.

Note:

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:

typenamelabelcalculation
texthead_nameName of the household head
calculatehead_name_cleantranslate(normalize-space(${head_name}), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
textnational_idNational ID number
calculatenational_id_nospacetranslate(${national_id}, ' ', '')
noteshow_cleanHousehold head: ${head_name_clean}. ID: ${national_id_nospace}
surveychoicessettings

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:

typenamelabelconstraintconstraint_message
textbrand_firstWhich brand of tea do you buy most often?
textbrand_confirmPlease confirm the brand of tea you buy most oftentranslate(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.
surveychoicessettings

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:

typenamelabelcalculation
textdistrictDistrict name
integerhh_numberHousehold number in the listing
calculatedistrict_uctranslate(normalize-space(${district}), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
calculatehh_idconcat(substr(${district_uc}, 0, 3), '-', ${hh_number})
noteshow_hh_idHousehold ID: ${hh_id}
surveychoicessettings

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:

typenamelabelcalculation
textcommuneNom de la commune
calculatecommune_uctranslate(${commune}, 'abcdefghijklmnopqrstuvwxyzéèêàâçôûî', 'ABCDEFGHIJKLMNOPQRSTUVWXYZÉÈÊÀÂÇÔÛÎ')
noteshow_communeCommune: ${commune_uc}
surveychoicessettings

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 wroteWrite insteadError you will see
upper(${name})translate(${name}, 'abc...z', 'ABC...Z')cannot handle function 'upper'
UPPER(${name})Same as abovecannot handle function 'UPPER'
translate(${name}, 'abc')Three arguments: the string, the lowercase list, the uppercase listcannot handle function 'translate'
translate(., ...) in the calculation column of the text question itselfA separate calculate rowCycle 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 lengthsLists of the same lengthNo 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.

Warning:

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:

PlatformConvert to uppercaseAllow only capitals
ODK (XLSForm)translate() in a calculate rowregex() in the constraint column
KoboToolbox FormbuilderAdd a Calculate question and type the translate() expressionregex() in the question's Validation Criteria, as a manually entered XLSForm code
KoboToolbox (XLSForm upload)Same as ODKSame as ODK
SurveyCTOupper(${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.

Tip:

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.

Build my first form free

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.