SurveyLoopr
Tips & Tricks
12 min read

Limit Digits and Decimal Places in XLSForm

Mehrab Ali
Mehrab AliPublished November 2, 2026

To limit the number of digits in XLSForm, give the number question a range in the constraint column: . >= 0 and . <= 999 allows at most three digits. To limit decimal places, add a regex() check on a decimal question: regex(., '^[0-9]+(\.[0-9]{1,2})?$') accepts 12, 12.5 and 12.75 but rejects 12.755. ODK, KoboToolbox and SurveyCTO all read this syntax. 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

How do I limit the number of digits in an integer?

Use a range, not a pattern. An integer with at most three digits is any number up to 999, and a four-digit year is any number from 1000 to 9999. A range is easier to read and also stops values that have the right length but make no sense:

typenamelabelrequiredconstraintconstraint_message
integerhh_sizeHow many people live in this household?yes. >= 1 and . <= 30Enter a number from 1 to 30.
integerplot_m2What is the size of the plot in square metres?yes. >= 0 and . <= 9999Enter at most 4 digits (0 to 9999).
integerlatrine_yearIn what year was the latrine built?yes. >= 1950 and . <= 2026Enter a 4-digit year from 1950 to 2026.
surveychoicessettings

An integer question cannot hold a leading zero: 007 is saved as 7. If the zeros matter, as in a code or an ID, use a text question (see the section on long numbers below). For more range rules, see the XLSForm constraint examples.

How do I limit decimal places on a decimal question?

Add regex() to the constraint. The pattern ^[0-9]+(\.[0-9]{1,2})?$ means: one or more digits, then optionally a point and one or two digits. A food price recorded in local currency and a child's weight in kilograms look like this:

typenamelabelrequiredconstraintconstraint_message
decimalrice_pricePrice of 1 kg of rice today (local currency)yes. > 0 and regex(., '^[0-9]+(\.[0-9]{1,2})?$')Enter the price with at most 2 decimal places.
decimalchild_weightChild's weight (kg)yes. >= 0.5 and . <= 60 and regex(., '^[0-9]+(\.[0-9])?$')Enter the weight from 0.5 to 60 kg with at most 1 decimal place.
surveychoicessettings

regex() reads the answer as text, so the point must be escaped as \.. An unescaped . matches any character, and the rule stops doing its job without any error. Keep a range next to the pattern: the pattern controls the shape, the range controls the size.

Warning:

A decimal question stores a number, not what was typed. 38.0 is saved as 38 and 12.50 as 12.5. So on a decimal question you can set a maximum number of decimal places, but not an exact one: a rule that demands one decimal place rejects 38.0. For exact decimals, use a text question, shown below.

How do I limit digits before and after the decimal point?

Put both counts in one pattern. A body temperature in °C has two digits before the point and at most one after. A mid-upper arm circumference (MUAC) in cm has one or two digits before and at most one after:

typenamelabelrequiredconstraintconstraint_message
decimaltemp_cBody temperature (°C)yes. >= 34 and . <= 43 and regex(., '^[0-9]{2}(\.[0-9])?$')Enter a temperature from 34 to 43 with at most 1 decimal place.
decimalmuac_cmMUAC (cm)yes. >= 6 and . <= 30 and regex(., '^[0-9]{1,2}(\.[0-9])?$')Enter MUAC from 6 to 30 cm with at most 1 decimal place.
surveychoicessettings

{2} means exactly two digits and {1,2} means one or two. This is the question behind one of the most viewed KoboToolbox forum threads on decimals, about a temperature field that should accept 99.9 and 101.2 but nothing longer.

How do I allow negative numbers?

Add an optional minus sign, -?, at the start of the pattern. GPS coordinates typed from a handheld unit are a common case: a latitude in Bangladesh is positive, but one in Peru is negative. This rule allows up to six decimal places, about 10 cm on the ground:

typenamelabelrequiredconstraintconstraint_message
decimallat_manualLatitude from the GPS unit (decimal degrees)yes. >= -90 and . <= 90 and regex(., '^-?[0-9]{1,2}(\.[0-9]{1,6})?$')Enter a latitude from -90 to 90 with at most 6 decimal places.
decimallon_manualLongitude from the GPS unit (decimal degrees)yes. >= -180 and . <= 180 and regex(., '^-?[0-9]{1,3}(\.[0-9]{1,6})?$')Enter a longitude from -180 to 180 with at most 6 decimal places.
surveychoicessettings

If the phone has GPS, a geopoint question is better than typed coordinates. See GPS and geopoint questions.

How do I require exactly N decimal places or more than 9 digits?

Use a text question with appearance set to numbers. The answer is kept exactly as typed, so 11.0 stays 11.0 and a leading zero stays. Then convert it to a number in a calculate row when you need to compare or add it:

typenamelabelrequiredappearanceconstraintconstraint_messagecalculation
texthb_textHaemoglobin reading (g/dL) as shown on the meteryesnumbersregex(., '^[0-9]{1,2}\.[0-9]$')Enter exactly 1 decimal place, for example 11.0
calculatehbnumber(${hb_text})
textmeter_noWater meter number (12 digits)yesnumbersregex(., '^[0-9]{12}$')Enter all 12 digits of the meter number.
surveychoicessettings

Use this for any number longer than the number widgets allow. In ODK Collect, an integer field accepts at most nine digits and a decimal field at most 15 characters, so a 12-digit meter number cannot be typed into either. Phone numbers have their own rules for prefixes and leading zeros, covered in phone number validation in XLSForm.

Note:

The numbers appearance shows a number keypad, but it does not stop other characters. On some keyboards the point and the minus sign are hard to find, so test the keypad on the phones your team will use. The regex() constraint is what blocks letters.

How do I round a calculated value to 2 decimal places?

Use round() in the calculation column. Values the form computes, such as an average or a per-person figure, often come out with many digits. A constraint cannot help here, because nobody types the value:

typenamelabelrequiredcalculation
integerhh_membersHow many people live in this household?yes
decimalfood_spendHow much did the household spend on food last week?yes
calculatespend_ppround(${food_spend} div ${hh_members}, 2)
notespend_pp_noteFood spending per person: ${spend_pp}
surveychoicessettings

round(x, 2) rounds to two decimal places, so 333.3333 becomes 333.33. To cut off without rounding, use int(x * 100) div 100, which follows an early answer on the ODK forum. For more on calculate rows, see XLSForm calculations.

Common mistakes and how to fix them

We ran each of these through pyxform and ODK Validate. The messages are what you will see in ODK Central, KoboToolbox or the XLSForm validator:

You wroteWrite insteadError you will see
regex(., '^[0-9]+(\.[0-9])?$'Close the bracket: regex(., '^[0-9]+(\.[0-9])?$')contains invalid constraint expression [regex(., '^[0-9]+(\.[0-9])?$'] Mismatched brackets or parentheses
regex(., ^[0-9]+$)Quote the pattern: regex(., '^[0-9]+$')Couldn't understand the expression starting at this point
. <= 999,9Use a point: . <= 999.9contains invalid constraint expression [. <= 999,9] Bad node
regex(., '^[0-9]+.[0-9]$')Escape the point: \.No error, but . matches any digit too, so 123 passes as if it had one decimal
regex(., '^[0-9]+\.[0-9]$') on a decimal questionA text question with numbers, or allow (\.[0-9])?No error, but 38.0 is saved as 38 and rejected
. * 100 = int(. * 100) for 2 decimalsregex(., '^[0-9]+(\.[0-9]{1,2})?$')No error, but 0.29 times 100 is 28.999... in binary, so valid prices are rejected
integer for a 10-digit IDtext with appearance numbersNo error, but Collect stops the entry at nine digits

The last four rows pass every check and fail in the field. Test each rule in the form preview with one value just inside and one just outside the limit. For other compile messages, see common XLSForm errors.

Does this work the same in ODK, KoboToolbox and SurveyCTO?

The constraint syntax is shared. Where you type it differs:

PlatformWhere the rule goesNotes
ODK Collect and Centralconstraint and constraint_message columnsInteger entry stops at 9 digits, decimal entry at 15 characters; use text with numbers for longer numbers
KoboToolbox (KoboCollect and Enketo)Same XLSForm columns; in the Formbuilder, the question's Validation CriteriaSame functions. KoboCollect is built on ODK Collect; Enketo in the browser has its own entry limits, so test both
SurveyCTOconstraint and constraint message columnsregex() and round() work the same. SurveyCTO names its numeric keypad appearances differently, so check its docs for text fields

In all three, a constraint only runs when the question is shown and answered. Rounding in a calculate row works everywhere and is often the simplest fix when the extra decimals only bother you in the export.

Should you limit decimals at all?

Sometimes rounding later is the better choice. ODK developers on the forum have long advised collecting the number as entered and rounding during analysis, because a constraint adds work for the enumerator. Limit decimals when the extra digits are a sign of a typing error: a weight of 12.755 kg from a scale that shows one decimal, or a price with more decimal places than the currency has. In those cases the rule catches the mistake while the respondent is still there.

Tip:

You can describe the rule in plain language instead: "weight in kg, between 0.5 and 60, at most one decimal place". LooprAI writes the range, the regex and the message, then compiles the form with pyxform before anything is saved, so an unescaped point or a missing bracket never reaches your form.

Build my first form free

Next: XLSForm constraint examples →

Frequently Asked Questions

How do I limit an integer to a number of digits in XLSForm?

Use a range in the constraint column. At most three digits is . >= 0 and . <= 999, and a four-digit year is . >= 1000 and . <= 9999. A range is easier to read than a pattern and also stops values that have the right length but are not plausible.

How do I limit a decimal question to 2 decimal places?

Add regex(., '^[0-9]+(\.[0-9]{1,2})?$') to the constraint. It accepts whole numbers and numbers with one or two decimal places, and rejects anything longer. Combine it with a range using and, for example . > 0 and the regex.

Why does my regex reject 38.0 on a decimal question?

A decimal question stores the number, not the text typed, so 38.0 is saved as 38 and a rule that demands one decimal place fails. Either make the decimal part optional with (\.[0-9])? or use a text question with appearance numbers.

How do I enter numbers longer than 9 digits in ODK Collect?

ODK Collect's integer field accepts at most nine digits and its decimal field 15 characters. For longer numbers, such as meter or ID numbers, use type text with appearance numbers and a regex() constraint like regex(., '^[0-9]{12}$').

How do I round a calculated value to 2 decimal places?

Use round() in the calculation column of a calculate row, for example round(${food_spend} div ${hh_members}, 2). To cut off extra digits without rounding, use int(x * 100) div 100. A constraint cannot round, because nobody types a calculated value.

Do these rules work the same in KoboToolbox and SurveyCTO?

Yes. regex(), round() and range constraints behave the same and go in the constraint column. In the KoboToolbox Formbuilder they go in Validation Criteria. Number entry limits differ between apps, so test long numbers in KoboCollect and Enketo before fieldwork.

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.