ODK to Stata: importing ODK Central data with labels, dates and repeat groups
ODK Central exports a zip of CSV files: one for the main table and one for each repeat group, linked by PARENT_KEY and KEY. In Stata, import each file with import delimited and case(preserve), convert dates with date() and clock(), add value labels from the XLSForm's choices sheet, split select_multiple answers into 0/1 variables, and merge repeat rows onto their interview with merge m:1. IPA's odkmeta and ARCED Foundation's odksplit automate most of this, and DataFlow downloads a labelled .dta with each repeat group as its own long table.
By Mehrab Ali, founder of DataFlow · Updated 8 November 2026
Five things catch people out when ODK data goes into Stata: variable names that change on import, dates that arrive as text, choice codes with no labels, multi-select answers packed into one column, and repeat groups in separate files. This guide covers what ODK Central's CSV export contains, the Stata code to import it properly, what IPA's odkmeta and ARCED Foundation's odksplit automate, and how DataFlow hands over the same data already labelled. The import, label and merge code was run in StataNow 19.5 on files laid out the way Central writes them; the odkmeta and odksplit examples follow each package's documented syntax.
What ODK Central exports
The download on a form's Submissions page offers the main data table alone, all data tables, or all data and attachments, with three options: Split “select multiple” choices into columns, Remove group names and Include fields not in the published Form. Filters set on the submissions table, such as a date range or review state, apply to the download. For a walk through the screen itself, and for encrypted forms, see the guide to exporting data from ODK Central by SurveyLoopr, managed ODK Central hosting from the makers of DataFlow.
| File in the zip | What it holds |
|---|---|
The main table, named after the form, e.g. household_survey.csv | One row per submission: SubmissionDate first, then your questions, then KEY (the submission's instance ID), SubmitterID, SubmitterName, AttachmentsPresent, AttachmentsExpected, Status, ReviewState, DeviceID, Edits and FormVersion |
One per repeat group, e.g. household_survey-members.csv | One row per repeat: its questions, then PARENT_KEY and KEY. PARENT_KEY is the KEY of the row it belongs to: the submission for a top-level repeat, or the outer repeat's row for a nested one |
files/ | Media, in one folder per submission |
Inside the files:
- Headers carry the group path. A question in group
hhishh-hhsize; Remove group names makes ithhsize. - Answers are codes. A select_one holds the choice's name from the XLSForm, not its label, and a select_multiple holds the chosen names separated by spaces, such as
1 3. - The split option adds one column per choice, named like
crops/1, with 1 when chosen. Central writes 0 in these columns when the question wasn't answered at all, so a 0 can mean “not chosen” or “not asked”; check the original column. - A geopoint becomes four columns:
-Latitude,-Longitude,-Altitudeand-Accuracyafter the question name. - Dates and times are text, as the device sent them:
2026-10-14for a date, and2026-10-14T08:00:00.000+06:00for a date and time, with the device's offset from UTC.SubmissionDateis the server's time in UTC, ending inZ.
Importing the CSV in Stata
Names, line breaks and long text
import delimited lowercases variable names unless you add case(preserve), so KEY, PARENT_KEY and SubmissionDate arrive as key, parent_key and submissiondate, and any do-file that refers to KEY stops working. Headers that aren't valid Stata names are translated, and the original header is kept as the variable label: in StataNow 19.5, hh-hhsize became hhhhsize, crops/1 became crops1, and a long name was cut to 32 characters. Ticking Remove group names keeps headers short, unless two groups use the same question name.
Add bindquote(strict) so a text answer with a line break stays in its own row; without it, the test file gained an extra, broken observation. maxquotedrows(unlimited) lifts the default limit of 20 lines inside one quoted answer. Long text answers are fine: import delimited stores them as strL when that saves space. For IDs with leading zeros, force those columns to text with stringcols().
* The main table from Central's CSV export
import delimited using "household_survey.csv", varnames(1) case(preserve) ///
bindquote(strict) maxquotedrows(unlimited) clearDates and times
* A date question: "2026-10-14"
generate interview_date = date(today, "YMD")
format interview_date %td
* Date and time with the device's offset: "2026-10-14T08:00:00.000+06:00"
generate double start_tc = clock(subinstr(substr(start, 1, 19), "T", " ", 1), "YMDhms")
generate double end_tc = clock(subinstr(substr(end, 1, 19), "T", " ", 1), "YMDhms")
format start_tc end_tc %tc
generate duration_min = (end_tc - start_tc) / 60000The first 19 characters give the time on the device's clock without the offset; the same line on SubmissionDate gives the server's time in UTC. Keep clock values in double variables, or they lose precision.
Value labels from the XLSForm
The export holds codes; the labels are in the XLSForm's choices sheet. Read it once and save the labels as a do-file to run after any import:
import excel using "household_survey.xlsx", sheet("choices") firstrow allstring clear
keep if !missing(list_name) & !missing(real(name))
forvalues i = 1/`=_N' {
label define `=list_name[`i']' `=name[`i']' `"`=label[`i']'"', modify
}
label save using "choice_labels.do", replace
* After importing the CSV
do "choice_labels.do"
label values village villageStata value labels need numeric codes, so this keeps the choices whose names are numbers; text codes such as maize need encode or a recode. If the labels are in a language column such as label::English (en), import excel names it labelEnglishen; use that instead of label. List names have to be valid Stata names.
select_multiple
* "1 3" gives crops_1 = 1, crops_2 = 0, crops_3 = 1; a blank answer stays missing
tostring crops, replace
replace crops = "" if crops == "."
foreach code in 1 2 3 {
generate byte crops_`code' = strpos(" " + crops + " ", " `code' ") > 0 if !missing(crops)
}If every answer so far is a single choice, Stata reads the column as a number, hence tostring. The spaces around each code stop 1 matching inside 11.
Repeat groups
save "household.dta", replace
* Members: one row per person, with the household's answers alongside
import delimited using "household_survey-members.csv", varnames(1) case(preserve) ///
bindquote(strict) maxquotedrows(unlimited) clear
rename KEY member_key
rename PARENT_KEY KEY
merge m:1 KEY using "household.dta", keep(master match)
drop _merge
save "members.dta", replace
* Back at household level: how many members were listed
use "members.dta", clear
contract KEY, freq(members_listed)
merge 1:1 KEY using "household.dta", nogenerate
replace members_listed = 0 if missing(members_listed)Rename the repeat file's own KEY first, or it collides with the interview's. For a repeat inside a repeat, PARENT_KEY points to the outer repeat's row, so merge it onto that file's KEY. Comparing members_listed with the household size question is a quick consistency check.
odkmeta
odkmeta, written by Matthew White at Innovations for Poverty Action, writes a do-file that imports the CSV using the survey and choices sheets of the XLSForm. The do-file turns lists into value labels (adding a value for “other”), splits select_multiple questions, drops notes, formats date, time and datetime variables, attaches question labels as variable labels and notes, keeps the other columns of the survey sheet as characteristics, and merges repeat groups. It reads the two sheets saved as CSV files:
ssc install odkmeta
ssc install specialexp
odkmeta using "import_household.do", csv("household_survey.csv") ///
survey("survey.csv") choices("choices.csv") replace
do "import_household.do"It is free under the MIT licence and saves writing the labels out by hand. It hasn't changed in years: the SSC package is dated March 2015, the last change on GitHub was in April 2016, and the README doesn't mention ODK Central; it says odkmeta isn't designed for features specific to ODK variants such as SurveyCTO. It imports with insheet, and its help file lists where it stops: long or invalid field names can come in as v1, v2 and so on; long strings of digits, such as SIM serials, are read as numbers and lose precision; splitting a select_multiple fails on long names or names that already exist; and the reshape that merges repeat groups fails on long variable names. Renaming is left to you, in marked sections of the do-file.
The “invalid stub” errors reported on the ODK forum came from that split step, where the new names clashed with variables that already existed. One cause to rule out: if you exported with Central's split option, Stata turns crops/1 into crops1, the kind of name odkmeta's split wants to create. Export without that option when you use odkmeta.
odksplit
odksplit is ARCED Foundation's Stata module for the same job, free under the MIT licence and maintained since 2019. It works on data you have already imported: point it at the dataset and the XLSForm, and it labels the variables, attaches value labels from the choices sheet, splits each select_multiple question into labelled 0/1 variables, and adds the full question text as a note on each variable. It reads the XLSForm workbook itself, so there are no sheets to save as CSV first, and it labels in every language the form has: switch with label language. Since version 4.0 it also labels variables in repeat groups, and it can save the labelled dataset for you. It works with SurveyCTO data too.
net install odksplit, all replace ///
from("https://raw.githubusercontent.com/ARCED-Foundation/odksplit/master")
import delimited using "household_survey.csv", varnames(1) case(preserve) ///
bindquote(strict) maxquotedrows(unlimited) clear
save "household_raw.dta", replace
odksplit, survey("household_survey.xlsx") data("household_raw.dta") ///
label(English) save("household_labelled.dta") clear
label languageInstall it from GitHub, as above: the SSC copy is an older version. Three things to know. It needs variable names without group prefixes, so export from Central with Remove group names ticked. It clears the data in memory, which is why the example passes clear (without it, odksplit asks first). And it splits select_multiple answers itself (given SurveyCTO's 1/0 columns, it deletes them and makes them again), so leave Central's split option off. A dateformat() option takes MDY or DMY.
After the import: arceddataflow
Getting the data labelled is the start. For the rest of a project's Stata work, ARCED Foundation's arceddataflow (MIT licence) creates the do-files for the whole data flow, with a folder for the corrections sheets, so every survey round starts from the same structure. DataFlow grew out of it, and DataFlow and Stata high-frequency checks compares the two ways of working.
How DataFlow gets ODK data into Stata
DataFlow connects to ODK Central with a Central account, or through a SurveyLoopr account, and reads submissions from Central's OData feed rather than the CSV zip. Answers inside groups keep their question names, with the group path added only if two names would clash, and repeat groups arrive as numbered columns: age_1, age_2, and for a nested repeat job_hours_1_2. If the form was uploaded to Central as an XLSForm, DataFlow downloads it; otherwise you can add the XLSForm file yourself. Labels come from the form, so add it before downloading for Stata.
The download comes as Stata .dta, CSV, Excel or R. In the .dta:
- The file is format 118, read by Stata 14 and later. Names are made Stata-safe: letters, digits and underscores, at most 32 characters, no two the same.
- Each question's label becomes its variable label.
- select_one questions whose choice codes are all whole numbers become numeric with value labels, named after the choice list. Other choice answers stay as text codes.
- Each select_multiple keeps its original answer and gains one 0/1 variable per choice, such as
crops_1, labelled with the question and the choice, and blank when the question was blank. A code of-99becomescrops_n99. - In numeric variables, don't-know, refused and not-applicable codes, found from the choice labels (or
-999and-888by default), become the extended missing values.d,.rand.n. - Each repeat group is its own long table, such as
data_members.dta:parent_key(the interview's ID),instance(its position) and the repeat's questions, one row per filled repeat. Numbers are numeric and each variable carries its question label; choice answers keep their codes, without value labels or 0/1 splits. - Dates and times stay as text, as the server sent them, so the
date()andclock()lines above work on them too. - Text longer than 2,045 characters is cut in the .dta, with a warning; the CSV has it in full.
For ODK Central the interview's ID is __id in data.dta, so a repeat table merges like this:
use "data_members.dta", clear
rename parent_key __id
merge m:1 __id using "data.dta", keep(master match)The download also holds a codebook (on by default) with every variable's label, type, value labels, how often it is filled and where it came from, and corrections.csv, every correction with who made it, when and why. With the do-file option, on by default, it adds raw.csv, the submissions before any correction, and dataflow.do, which rebuilds the main dataset from it; repeat-group tables aren't rebuilt by the do-file. Personal data stays out of the download unless someone unlocks it, which is logged. If you run your checks in Stata today, ipacheck and Stata high-frequency checks compared with DataFlow covers how the two fit together. To see a download for yourself, the sample study on the download page works free and without an account.
Step by step
- Export from ODK Central. Download all data tables from the form's Submissions page. Tick Remove group names unless two groups share a question name (odksplit needs it ticked), and leave the select multiple split off if you use odkmeta or odksplit, which split it themselves.
- Import each CSV. Use import delimited with varnames(1), case(preserve), bindquote(strict) and maxquotedrows(unlimited), so KEY and PARENT_KEY keep their names and text answers with line breaks stay in one row.
- Convert dates and times. Use date(var, "YMD") for dates, and clock() on the first 19 characters, with the T replaced by a space, for dates with times. Store clock values as double and format them %tc.
- Add value labels from the XLSForm. Import the choices sheet, define one value label per list with numeric codes, save them with label save, and attach them with label values after each import.
- Split select_multiple answers. Make one 0/1 variable per choice with strpos on the space-padded answer, leaving it missing where the question was blank.
- Merge repeat groups. Rename the repeat file's KEY, rename PARENT_KEY to KEY, and merge m:1 onto the saved main table. Use contract to count repeat rows per interview and merge the count back 1:1.
Questions
How do I import ODK data into Stata?
Download all data tables from ODK Central as CSV, then import each file with import delimited using varnames(1), case(preserve) and bindquote(strict). Convert dates with date() and clock(), add value labels from the XLSForm's choices sheet, split select_multiple answers into 0/1 variables, and merge repeat-group files onto the main table with merge m:1 on PARENT_KEY renamed to KEY.
Why does KEY become key when I import ODK data into Stata?
import delimited lowercases variable names by default. Add case(preserve) to keep KEY, PARENT_KEY and SubmissionDate as they are in the file.
What is odkmeta?
odkmeta is a Stata program by Matthew White at Innovations for Poverty Action, installed with ssc install odkmeta. It writes a do-file that imports ODK CSV data using the XLSForm's survey and choices sheets: value labels, select_multiple splits, date formats, variable labels and merged repeat groups. It is MIT-licensed; the SSC version dates from 2015 and the GitHub code was last changed in 2016.
What is odksplit?
odksplit is a Stata module from ARCED Foundation, MIT-licensed, that labels ODK and SurveyCTO data already in Stata. It reads the XLSForm workbook directly, adds variable and value labels in every language the form has, splits select_multiple questions into labelled 0/1 variables, and adds full question text as notes. Install the current version from its GitHub repository; the SSC copy is older.
How do I merge ODK repeat groups in Stata?
Each repeat group is its own CSV. Import it, rename its KEY to something like member_key, rename PARENT_KEY to KEY, and run merge m:1 KEY using the saved main table. For a repeat nested in another repeat, PARENT_KEY matches the KEY of the outer repeat's rows.
Why are ODK dates text in Stata?
ODK Central writes dates and times into the CSV as text, such as 2026-10-14 or 2026-10-14T08:00:00.000+06:00. Convert dates with date(var, "YMD") and dates with times with clock() on the first 19 characters, after replacing the T with a space, stored as double.
Can DataFlow give me ODK Central data as a Stata file?
Yes. DataFlow reads submissions from ODK Central and downloads a .dta for Stata 14 and later, with variable labels from the form, value labels for numeric choice lists, 0/1 variables for select_multiple answers, don't-know codes as extended missing values, and each repeat group as its own long table with parent_key. Dates stay as text.