Phone Number Validation in XLSForm with regex()
To validate a phone number in XLSForm, make it a text question with the numbers appearance and put regex(., '^[0-9]{10}$') in its constraint column. That accepts exactly 10 digits, keeps a leading zero, and refuses letters, spaces and numbers that are too short or too long. Change 10 to your country's length, and add fixed first digits when every number starts the same way. Every example below compiles with pyxform and ODK Validate, the check ODK Central runs when you upload a form.
Which question type should I use for a phone number?
Use text with the numbers appearance, not integer:
| type | name | label | appearance | constraint | constraint_message |
|---|---|---|---|---|---|
| text | resp_phone | What is the respondent's mobile phone number? | numbers | regex(., '^[0-9]{10}$') | Enter exactly 10 digits with no spaces or dashes. |
An integer question goes wrong in two ways that only show up in the data:
- The leading zero disappears.
0244310243is saved as the number 244310243. A Ghanaian enumerator asked on the KoboToolbox forum how to keep that zero; the thread has over 5,000 views. - Long numbers do not fit. The ODK documentation says integers are limited to 9 digits. A Nigerian KoboToolbox user reported that Collect stopped at 8 or 9 digits when they tried to type an 11-digit number.
The numbers appearance opens the number keypad but stores the answer as text, so the zero stays. The keypad still allows hyphens, dots and commas, so the regex() constraint is what keeps the answer to digits only.
How do I require exactly 10 digits?
regex(., '^[0-9]{10}$') means: from the start (^), exactly 10 characters from 0 to 9 ([0-9]{10}), then the end ($). The dot . is the current answer, and the pattern must be in single quotes.
Change the number in the braces for your country. These are common mobile number lengths when typed without the country code:
| type | name | label | appearance | constraint | constraint_message |
|---|---|---|---|---|---|
| text | phone_9 | Mobile number (9 digits) | numbers | regex(., '^[0-9]{9}$') | Enter exactly 9 digits. |
| text | phone_10 | Mobile number (10 digits) | numbers | regex(., '^[0-9]{10}$') | Enter exactly 10 digits. |
| text | phone_11 | Mobile number (11 digits) | numbers | regex(., '^[0-9]{11}$') | Enter exactly 11 digits. |
Always keep both ^ and $. Without them, regex() only has to find 10 digits somewhere inside the answer, so a 12-digit number passes too. That is the most common reason a phone constraint "does not work" on the ODK and KoboToolbox forums.
How do I check that the number starts with 0 or a set prefix?
Write the fixed digits before the bracket and reduce the count by the same amount. All three rows below are 10 or 11 digits in total:
| type | name | label | appearance | constraint | constraint_message |
|---|---|---|---|---|---|
| text | phone_start0 | Mobile number (10 digits starting with 0) | numbers | regex(., '^0[0-9]{9}$') | Enter 10 digits starting with 0. |
| text | phone_start9 | Mobile number (9 digits starting with 9) | numbers | regex(., '^9[0-9]{8}$') | Enter 9 digits starting with 9. |
| text | phone_bd | Mobile number (11 digits starting with 013 to 019) | numbers | regex(., '^01[3-9][0-9]{8}$') | Enter 11 digits starting with 013 to 019. |
^0[0-9]{9}$is a 0 followed by 9 more digits, as in Ghana (0244310243).^9[0-9]{8}$is a 9 followed by 8 more digits. A forum user first wrote{9}here and Enketo said "Value not allowed": with the 9 in front, that pattern needs 10 digits.^01[3-9][0-9]{8}$is a Bangladesh mobile number:01, one operator digit from 3 to 9, then 8 digits.
Count the digits before you publish. The fixed digits plus the number in braces must equal the full length.
How do I accept numbers of two different lengths?
Use {min,max}. regex(., '^[0-9]{9,10}$') accepts 9 or 10 digits and nothing else. For two formats that differ by more than length, join two patterns with | inside a group:
| type | name | label | appearance | constraint | constraint_message |
|---|---|---|---|---|---|
| text | phone_9_or_10 | Mobile number (9 or 10 digits) | numbers | regex(., '^[0-9]{9,10}$') | Enter 9 or 10 digits. |
| text | phone_local_or_intl | Mobile number (0 or 880 first) | numbers | regex(., '^(0|880)1[3-9][0-9]{8}$') | Enter the number starting with 0 or with 880. |
The second row accepts 01712345678 and 8801712345678. Leave the + out: the number keypad has no plus key. If the country code matters for analysis, ask it as its own question rather than parsing it out of one field later.
How do I make the enumerator type the number twice?
A mistyped digit passes any format check. For numbers you will call back, such as a follow-up survey or a cash transfer, add a second question whose constraint compares it with the first:
| type | name | label | appearance | constraint | constraint_message |
|---|---|---|---|---|---|
| text | hh_phone | Household head's mobile number | numbers | regex(., '^[0-9]{10}$') | Enter exactly 10 digits. |
| text | hh_phone_confirm | Type the same number again to confirm. | numbers | . = ${hh_phone} | The two numbers do not match. Check both and correct the wrong one. |
Put the two questions on separate screens. If both are visible together, enumerators tend to copy the first answer instead of asking the respondent again.
Read the number back to the respondent before moving on. The confirm field catches typing slips; reading it aloud catches a respondent who gave a neighbour's or an old number.
What if the respondent has no phone?
Ask first, then show the phone question only to people who have one. A required phone question with no way out forces enumerators to invent a number that passes the check:
| type | name | label | appearance | relevant | required | constraint | constraint_message |
|---|---|---|---|---|---|---|---|
| select_one yes_no | has_phone | Does anyone in the household own a mobile phone? | yes | ||||
| text | contact_phone | What is the mobile number we can reach the household on? | numbers | ${has_phone} = '1' | yes | regex(., '^[0-9]{10}$') | Enter exactly 10 digits. |
| list_name | name | label |
|---|---|---|
| yes_no | 1 | Yes |
| yes_no | 0 | No |
Because the phone question is hidden when the answer is No, its required and constraint never run for those households. More on relevant is in the XLSForm skip logic guide.
Common phone number validation mistakes
These are the messages pyxform and ODK Validate return, which is what you see when ODK Central, KoboToolbox or the XLSForm converter rejects a form:
| You wrote | Write instead | Error you will see |
|---|---|---|
integer type | text with appearance numbers | No error, but the leading 0 is lost and long numbers cannot be typed |
regex(., '[0-9]{10}') | regex(., '^[0-9]{10}$') | No error, but numbers longer than 10 digits pass |
regex(., ^[0-9]{10}$) | regex(., '^[0-9]{10}$') | Couldn't understand the expression starting at this point |
regex(., '^[0-9]{10,11,12}$') | regex(., '^[0-9]{10,12}$') | No error, but no phone number passes |
regex(., '^[0-9]{10}$' | regex(., '^[0-9]{10}$') | Mismatched brackets or parentheses |
. = ${phone_number} (typo) | The exact question name | Reference variables must contain a name from the 'survey' sheet. Could not find the name 'phone_number'. |
The silent rows cost the most, because the form uploads and the bad numbers only show up when someone tries to call. For other messages, see common XLSForm errors, or paste your form into the free XLSForm validator.
Does this work the same in ODK, KoboToolbox and SurveyCTO?
Yes. regex() is part of the expression language on all three, so the patterns on this page copy across unchanged. What differs is where you type them and which keypad appearances exist:
| Platform | Where the rule goes | Number keypad on a text question |
|---|---|---|
| ODK (XLSForm) | constraint column | appearance numbers |
| KoboToolbox Formbuilder | Question settings, Validation Criteria, then the manual XLSForm code option | Same as ODK when deployed to KoboCollect or Enketo |
| KoboToolbox (XLSForm upload) | Same as ODK | appearance numbers |
| SurveyCTO | constraint column | numbers, numbers_decimal or numbers_phone |
SurveyCTO's documentation notes that different devices show different keypads, so test the appearance on the phones your team will use. The XLSForm appearance guide lists the other text appearances.
Why validate phone numbers at all?
A phone number is often the only way back to a respondent: for a follow-up round, a back-check, a referral or a cash transfer. A wrong digit costs more than a wrong answer, because the household is lost from the panel. A strict format check plus a confirm field takes two rows and removes most of those errors before the interview ends. The wider set of rules is in the XLSForm constraint examples.
You can describe it instead: "the phone number must be 11 digits starting with 01, and ask for it twice". LooprAI writes the text question, the numbers appearance, the regex constraint and the confirm field, and compiles the form with pyxform before anything is saved.
Next: more XLSForm constraint examples →
Frequently Asked Questions
How do I validate a phone number in XLSForm?
Use a text question with the numbers appearance and put regex(., '^[0-9]{10}$') in the constraint column. It accepts exactly 10 digits and refuses letters, spaces and wrong lengths. Change 10 to your country's length and add a constraint_message saying what is expected.
Why does my phone number lose its leading zero in ODK or Kobo?
The question is an integer, so it is stored as a number and the zero is dropped. Change the type to text and set the appearance to numbers. The number keypad still opens, but the answer is saved as typed, zero included.
Why can I not type more than 9 digits in an integer question?
ODK limits integer questions to 9 digits, which is too short for most phone numbers. Use a text question with the numbers appearance instead, and limit the length with a regex() constraint such as regex(., '^[0-9]{11}$') for 11 digits.
Why does my phone regex accept numbers that are too long?
The pattern has no ^ at the start and $ at the end. Without them, regex() only needs to find the pattern somewhere inside the answer, so 12 digits contain a valid 10-digit match. Write regex(., '^[0-9]{10}$') so the whole answer must match.
How do I allow phone numbers of 9 or 10 digits?
Use a range in the braces: regex(., '^[0-9]{9,10}$') accepts 9 or 10 digits and nothing else. Write the range as {min,max}. A list such as {10,11,12} is not valid range syntax, and no phone number will pass it.
Does phone number validation work the same in KoboToolbox and SurveyCTO?
Yes. All three support regex() in the constraint column, so the patterns copy across unchanged. In the KoboToolbox Formbuilder, enter it under Validation Criteria. SurveyCTO also offers numbers_phone and numbers_decimal appearances for the number keypad.
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.