Editing ODK submissions after they're sent, without losing track

Once an ODK form is sent, you can correct it in two places: in ODK Central, where Administrators and Project Managers edit the submission in the browser and each edit becomes a new version shown in its activity feed, or after export, in a corrections log that your cleaning code applies to the raw data. Collect can edit a sent form only if the form allows it (client_editable, from Collect v2025.2) and the filled form is still on the device. Pick one place for corrections, and record each one with the submission ID, question, old value, new value, reason, who and when.

By Mehrab Ali, founder of DataFlow · Updated 8 November 2026

A typo spotted in the office, a household ID entered wrong, an age confirmed by phone: sooner or later every ODK project has to change data that has already reached the server. The question comes up again and again on the ODK forum. The answer has two parts: where you make the change, and how you record it, so the dataset can be rebuilt and every change checked later.

What ODK Central offers

Editing a submission in the browser

In the submissions table, or on a submission's detail page, press Edit. The submission opens in Central's web forms: Enketo, or ODK Web Forms, which is the default for forms created in Central v2026.2.0 or later. Only Administrators and Project Managers can edit and review submissions; Project Viewers and Data Collectors can't. ODK's documentation notes that web forms don't always behave exactly like Collect, so try an edit on a test submission before the team relies on it.

Versions, the activity feed and comments

Saving an edit creates a new version that replaces the old one whole; nothing is merged. The ID the submission was first sent with stays the same across versions (__id in OData, KEY in the CSV export), while each version gets its own instanceID, so meta-instanceID changes with every edit. Central accepts an update only if it replaces the current version; its API answers 409 Conflict to any other. Exports and OData give the current version, and the CSV export's Edits column counts each submission's edits.

Since Central 1.3, the activity feed on the submission's page shows the changes between versions, though ODK warns that these are differences, not the exact actions taken. Central then suggests a comment describing the edit. It's optional, and without it the feed shows what changed but not why. Administrators, Project Managers and Project Viewers can comment. Earlier versions are kept and can be read through the API; the documentation says viewing them in the browser will come in a later version of Central.

Review states

StateSet byMeaning
ReceivedCentralEvery new submission
EditedCentralSet whenever anyone edits the submission
Has issuesProject Managers, AdministratorsThe submission has problems
ApprovedProject Managers, AdministratorsThe submission is accepted
RejectedProject Managers, AdministratorsThe submission is rejected, but still exported unless you filter it out

Review states arrived in Central 1.2. They don't make anything happen on their own: they are labels, with an optional note on each change, that the table, the CSV download and the OData feed can be filtered by. ODK's guide to managing submissions in Central covers them in full, and SurveyLoopr, managed ODK Central hosting from the makers of DataFlow, has an ODK submission review checklist for working through them.

What Collect can do once a form is sent

By default, nothing. A finalized form can be viewed or sent from Ready to send but not edited, and a sent form stays viewable in the Sent list until it is deleted, which happens as soon as it is sent if Delete after send is on.

From ODK Collect v2025.2.0, with ODK Central v2025.1.4 or later, a form can allow edits to finalized and sent submissions that are still on the device. It is off by default; turn it on with a client_editable column in the settings sheet:

form_titleform_idversionclient_editable
Household surveyhousehold2026110801true

Each edit sent from Collect appears in the submission's activity feed in Central. Once someone edits the submission in Central, it can no longer be edited from Collect, and Collect edits use the form version the interview was filled in on, not the latest. See ODK's page on managing forms in Collect.

Edit in Central, or correct in your code?

Edit in ODK CentralCorrect in your code
Who can change a valueAdministrators and Project Managers on the projectWhoever maintains the cleaning code and the log
Where the reason goesA comment, if someone writes oneA column in the log, required for every row
The original answerKept as an earlier version, read through the APIThe raw export, kept as downloaded
A new exportCarries the edited answersReplaces the raw file; the code applies the log again
Two people, one valueOnly an edit to the current version is acceptedTwo copies of the log can disagree, and the last line run wins
Who sees the fixEveryone reading Central: exports, OData, dashboardsOnly people who run the code

Editing in Central suits teams whose supervisors already work there and who want every download and dashboard to show the corrected value. Correcting in code suits teams that must rebuild the dataset from what was collected, show a reviewer every change in one file, or keep edit rights on the server to a few people.

What goes wrong is doing both without a rule. A do-file line that sets an age to 34 keeps doing so after someone fixes the same answer to 43 in Central, and nothing says the two disagree. Corrections keyed on meta-instanceID or a row number stop matching, without an error, once a submission is edited or a new export comes in another order. If you use both, split them by kind: answers confirmed with the field team are edited in Central with a comment; analysis decisions such as recodes, outlier rules and dropped interviews stay in code, which checks the old value before changing anything.

A corrections log that leaves the raw data alone

This method works whatever the platform: keep each download exactly as it came, write every correction as a row in a log, and let code apply the log to make the clean dataset. Nobody edits the raw file by hand, so every correction can be checked, undone and replayed.

ColumnWhat goes in it
keyThe submission ID that survives edits: KEY in a Central CSV export, __id in OData. Not meta-instanceID, and never a row number.
variableThe question's variable name in the imported data
old_valueThe value as it is in the raw data
new_valueThe corrected value; empty to blank the answer
reasonWhy, specific enough for someone else to check: “41 typed for 4, confirmed with the respondent by phone”
corrected_by, corrected_atWho decided, and when
statusactive or undone: undo a correction by changing its status, never by deleting the row

In Stata, applying the log to the raw export takes a short loop:

* 1. Read the corrections that are in effect
import delimited using "corrections.csv", varnames(1) stringcols(_all) clear
keep if status == "active"
local n = _N
forvalues i = 1/`n' {
    local key`i' = key[`i']
    local var`i' = variable[`i']
    local old`i' = old_value[`i']
    local new`i' = new_value[`i']
}

* 2. Start from the raw export, as downloaded, read as text
import delimited using "raw/household_raw.csv", varnames(1) case(preserve) ///
    stringcols(_all) bindquote(strict) clear

* 3. Apply each correction only where the old value still matches
forvalues i = 1/`n' {
    quietly count if KEY == "`key`i''" & `var`i'' == "`old`i''"
    if r(N) != 1 {
        display as error "Correction `i' skipped: `var`i'' of `key`i'' does not match"
        continue
    }
    quietly replace `var`i'' = "`new`i''" if KEY == "`key`i''"
}

* 4. Numbers back to numbers, then save under a new name
destring hhsize age_head income_monthly, replace
save "clean/household_clean.dta", replace

Reading both files as text keeps an ID such as 0123 as typed and compares values exactly as written; only the number questions are converted at the end. The count line matters most: if the raw value is no longer the log's old value, because the submission was edited in Central or the ID repeats, the correction is skipped with a message instead of overwriting newer data. IPA's readreplace makes the replacements in one command but has no option for the old value, so keep a check like this while the source can still change. Run the whole file from the raw export every time.

How DataFlow keeps corrections traceable

DataFlow is a desktop app that checks survey data from SurveyLoopr, SurveyCTO, ODK Central and KoboToolbox on your own computer, built around the method above. With ODK Central, each sync reads the submissions sent or edited since the last one from the OData feed. An edit made in Central keeps the submission's __id, so the new version replaces the copy DataFlow had instead of arriving as a second interview, and the review state comes in as a review_status column. Apart from signing in, every request DataFlow makes to Central is a read: it never changes a submission or its review state.

Corrections made in DataFlow never touch the raw submissions. The clean data is rebuilt from the raw copy plus every correction still in effect, in order. A correction can't be saved without a reason, and it records the interview, variable, old and new value, who made it and when; a flag it fixes is marked corrected. Undo a correction from the interview's panel and it stays in the log marked undone, its flag reopens, and the next check run decides whether it is still a problem.

DataFlow's raw copy is what the server sends. If an answer corrected in DataFlow is later edited in Central, the edited version becomes the raw value and DataFlow's correction still applies on top, so the rule above holds here too. On a team, two different fixes to the same value, made without seeing each other, aren't applied: the value shows as it was before them until someone chooses one. The other is undone, and both stay in the log.

For supervisors who work in Excel, Send to a supervisor saves a check's open flags as a sheet with columns for the corrected value and the reason. Bring back corrections previews the filled sheet row by row, skips rows it can't apply, such as one whose recorded value changed since the sheet was made, and applies the rest as one change that can be undone whole. The comparison with Excel describes the round trip.

Every import, check run, correction, undo and download goes into an activity log where each entry carries a hash of the one before, so editing a past entry breaks the chain, and the log shows where. Every download includes corrections.csv, with each correction's interview, variable, old and new value, reason, who and when, undone ones included (corrections to protected personal data appear only when that data is unlocked for the download). With the do-file option, on by default, it also holds raw.csv, the submissions before any correction, and dataflow.do, which rebuilds the dataset in Stata with one replace line per correction in effect, each with its reason, who and the date (the Stata comparison shows one). To try it, the sample study on the download page is free and needs no account.

Step by step

  1. Choose where corrections are made. Before fieldwork, decide whether answers are corrected in ODK Central, in your cleaning code, or split by kind, and who may make them.
  2. Keep every download unchanged. Save each CSV export or OData pull as it came and never edit it by hand. It is the raw data.
  3. Identify submissions by an ID that survives edits. Use KEY in a Central CSV export or __id in OData, not meta-instanceID, which changes with every edit.
  4. Log each correction. Record the ID, variable, old value, new value, reason, who and when, with a status so a correction can be undone without deleting it.
  5. Explain every edit made in Central. Leave a comment on the submission saying what changed and why, and set the review state.
  6. Apply the log with code. Start from the raw export, check that each old value still matches, replace it, and report any correction that no longer matches.
  7. Rebuild the clean data in one run. Before each analysis or delivery, run the cleaning from the raw export and share the corrections log with the data.

Questions

Can I edit an ODK submission after it has been sent?

Yes, in ODK Central. Administrators and Project Managers press Edit on the submission, change it in the browser and save; Central keeps it as a new version and sets the review state to Edited. Collect can edit a sent form only when the form has client_editable set to true (Collect v2025.2 and later) and the filled form is still on the device.

Does ODK Central keep the original data after an edit?

Yes. Each edit creates a new version, and earlier versions can be read through the API. Since Central 1.3, the submission's activity feed shows the changes between versions. Exports and OData give the current version only, and the CSV export's Edits column counts the edits.

Why did the instanceID change after I edited a submission?

Each version of a submission gets its own instanceID, shown in meta-instanceID. The ID the submission was first sent with stays the same across versions: KEY in the CSV export and __id in OData. Match corrections and merges on that one.

Should I correct ODK data in Central or in Stata?

Either works; doing both without a rule doesn't. Central makes the fix visible to everyone who reads the server. A corrections log applied by a do-file keeps the raw export untouched and every change in one file. If you use both, edit field-confirmed answers in Central with a comment and keep analysis decisions in code.

What should a survey data corrections log contain?

One row per change: the submission ID that survives edits, the variable, the old value, the new value, the reason, who decided and when, and a status, so a correction is undone by marking it rather than deleting it.

Does DataFlow pick up edits made in ODK Central?

Yes. Each sync reads submissions sent or edited since the last one, and an edit replaces DataFlow's copy because the submission keeps its __id. Corrections made in DataFlow stay on top of it. DataFlow never writes to the server.

Read next