SurveyLoopr
Tips & Tricks
10 min read

Phone Number Validation in XLSForm with regex()

Mehrab Ali
Mehrab AliPublished October 23, 2026

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.

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

Which question type should I use for a phone number?

Use text with the numbers appearance, not integer:

typenamelabelappearanceconstraintconstraint_message
textresp_phoneWhat is the respondent's mobile phone number?numbersregex(., '^[0-9]{10}$')Enter exactly 10 digits with no spaces or dashes.
surveychoicessettings

An integer question goes wrong in two ways that only show up in the data:

  • The leading zero disappears. 0244310243 is 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:

typenamelabelappearanceconstraintconstraint_message
textphone_9Mobile number (9 digits)numbersregex(., '^[0-9]{9}$')Enter exactly 9 digits.
textphone_10Mobile number (10 digits)numbersregex(., '^[0-9]{10}$')Enter exactly 10 digits.
textphone_11Mobile number (11 digits)numbersregex(., '^[0-9]{11}$')Enter exactly 11 digits.
surveychoicessettings

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:

typenamelabelappearanceconstraintconstraint_message
textphone_start0Mobile number (10 digits starting with 0)numbersregex(., '^0[0-9]{9}$')Enter 10 digits starting with 0.
textphone_start9Mobile number (9 digits starting with 9)numbersregex(., '^9[0-9]{8}$')Enter 9 digits starting with 9.
textphone_bdMobile number (11 digits starting with 013 to 019)numbersregex(., '^01[3-9][0-9]{8}$')Enter 11 digits starting with 013 to 019.
surveychoicessettings
  • ^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:

typenamelabelappearanceconstraintconstraint_message
textphone_9_or_10Mobile number (9 or 10 digits)numbersregex(., '^[0-9]{9,10}$')Enter 9 or 10 digits.
textphone_local_or_intlMobile number (0 or 880 first)numbersregex(., '^(0|880)1[3-9][0-9]{8}$')Enter the number starting with 0 or with 880.
surveychoicessettings

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:

typenamelabelappearanceconstraintconstraint_message
texthh_phoneHousehold head's mobile numbernumbersregex(., '^[0-9]{10}$')Enter exactly 10 digits.
texthh_phone_confirmType the same number again to confirm.numbers. = ${hh_phone}The two numbers do not match. Check both and correct the wrong one.
surveychoicessettings

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.

Tip:

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:

typenamelabelappearancerelevantrequiredconstraintconstraint_message
select_one yes_nohas_phoneDoes anyone in the household own a mobile phone?yes
textcontact_phoneWhat is the mobile number we can reach the household on?numbers${has_phone} = '1'yesregex(., '^[0-9]{10}$')Enter exactly 10 digits.
surveychoicessettings
list_namenamelabel
yes_no1Yes
yes_no0No
surveychoicessettings

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 wroteWrite insteadError you will see
integer typetext with appearance numbersNo 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 nameReference 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:

PlatformWhere the rule goesNumber keypad on a text question
ODK (XLSForm)constraint columnappearance numbers
KoboToolbox FormbuilderQuestion settings, Validation Criteria, then the manual XLSForm code optionSame as ODK when deployed to KoboCollect or Enketo
KoboToolbox (XLSForm upload)Same as ODKappearance numbers
SurveyCTOconstraint columnnumbers, 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.

Tip:

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.

Build my first form free

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.