Guide 10 · Developer docs

Forms and data capture

Building forms with the Forms module: how a form is put together and placed on a page, validating and branching, presenting it, where submissions are stored, and what happens after one arrives, from jobs and records to CRMs and payments.

L1–L2
Unit 05 · Storing submissions

Reading and exporting submissions

The three levels a submission is stored at and the flattened answers; reading submissions in the admin and through the API, with a real response; and exporting them without turning an export into a leak.

Who this is for. The integrations engineer reading submissions from another system, and the site builder who sets up exports for the client. Afterwards you can find any answer in a submission, list submissions by form, state and date through the API, and export them with only the fields the task needs.

What a submission stores

Every time a form is sent, the platform stores a submission, at three levels, one inside the other:

LevelOne perHolds
Form submissionVisitor's go at the formIts id; a 60-character token; the form; the revision of the form it was made against; the site; created and modified; completed, the moment it was finished, or empty; createdBy, the signed-in user who sent it, or empty for a visitor
Step submissionStep the visitor submittedThe step, and whether every answer on it was valid
Element submissionInput on a submitted stepThe field's name, the element, whether the answer was valid, and the answer as a list of values

Four facts about this shape catch people:

  • Submissions are saved before they are finished. On a page-per-step form every step is saved as it is submitted, and a submission whose answers fail validation is saved too, with the failing answers marked invalid. completed is set only when every input is valid and the last step is done. Abandoned forms, failed attempts and, unless the form requires a completed captcha, failed captchas all leave incomplete submissions behind. Anything that counts or acts on submissions must decide whether it means complete ones, and it almost always does.
  • Answers are keyed by the name the field had at the time. Rename phone to mobile and older submissions still say phone; code reading answers by name sees no mobile on any submission made before the rename.
  • Nothing else is recorded. No IP address, browser, page URL, referrer or consent time. If the client needs one, it must be a field: a Hidden input filled by a client-side helper, or a question. Decide this before launch, because it cannot be added to submissions already made.
  • The token is a key. It continues a submission that is not yet complete (Submitting a form through the API), and it is part of every uploaded file's address (File uploads). Never print it anywhere but the form's own hidden field, and never put it in an email.

The flattened answers: data

Navigating three levels to read one answer is rarely needed. Every submission read through the API, or by a job, also has data: the answers keyed by field name. The value depends on the element:

ElementValue in data
Text, email, phone, textarea, hidden, date, number, slider, radio, a single select, a single checkboxA string (empty when not answered)
Checkbox with several options, a multi-selectA list of the chosen values
RangeA list of two numbers, lower first
FileA list with one object per file: type (image or file), id, name and link
PaymentAn object with invoiceId, amount and transactionId (Taking payments)
CaptchaAn empty string: its answer is checked and thrown away

Use data for answers. Go down to element submissions only for what data drops: which answers were valid, or which element an answer belongs to after a rename. Go to step submissions to see how far an incomplete submission got.

Reading submissions

In the admin

In the Forms listing, the Submissions column shows each form's count, and the count links to that form's submissions. The listing shows the form, the user who sent each one, Started (the date it was created) and Completed, and filters by form and by either date; a submission with no completed date is an incomplete one. Each row can be opened to read its answers, or removed. Who can see this is set by the admin user's permissions on the Forms module, so give the people who read enquiries that permission and nothing broader.

Through the API

The REST API lists submissions per form, or for every form:

HTTP
GET /cdna-api/forms/submissions/{form id}
GET /cdna-api/forms/submissions

It takes the list parameters every listing takes (limit, offset, orderBy, properties and field filters; Filters, ordering and paging). The filters an integration needs most:

To getParameter
Complete submissions only!completed=NULL
Incomplete ones onlycompleted=NULL
Created after a moment (exclusive)>created=2026-08-01 00:00:00
Created before a moment (exclusive)<created=2026-09-01 00:00:00
Containing an answer%elementSubmissions.value=someone@example.com (used for data requests)
Oldest firstorderBy=created:ASC

The request needs the credentials of a backend user with permission to read form submissions: an OAuth access token, the preferred way, or an API key, the older one (Authentication). Without credentials the answer is 401; a user without the permission is refused, with a 404 by default (Responses and errors). A complete request, against a support form on this documentation site:

Complete submissions of form 12 since a date, oldest first
GET /cdna-api/forms/submissions/12?!completed=NULL&>created=2026-08-01 00:00:00&orderBy=created:ASC&limit=1&properties={id,completed,created,createdBy{id},data} HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Response 200 OK
{
  "result": "success",
  "totalCount": 2,
  "data": [
    {
      "id": 2152,
      "completed": "2026-08-24T06:12:18+00:00",
      "created": "2026-08-24T06:12:18+00:00",
      "createdBy": null,
      "data": {
        "name": "Test User",
        "email": "test@coredna.com",
        "category": "Content & pages",
        "priority": "Low — a question",
        "message": "Test submission, please ignore."
      }
    }
  ]
}
Real response from this documentation site

With properties given, data is the list itself. Without it, data is an object holding the form (form) and the list (formSubmissions), and each submission carries every field: its token, its revision, the ids of its step and element submissions, data, and result, which describes what the form showed on completion. Name the properties you need: the answer is smaller, and the token stays out of your logs.

Page through a large set by created, not by offset: remember the newest created you have processed and ask for >created that value next time, so new submissions arriving between two requests cannot shift a page under you (Page through a large API result set without timing out).

The API cannot delete a submission, and cannot change a completed one. The other three submission routes send a submission, continue one by its token, and ask whether one is complete; they take no credentials, and Submitting a form through the API covers them.

Exporting

The admin exports a form's submissions as CSV: Export submissions is on the form's row in the Forms listing, and on its submissions listing. The export screen asks for three things:

  • Elements to export. One box per field name. The active revision's fields are ticked; names that only older revisions had are offered unticked, which is how answers stored under an old name are included.
  • Submission status: completed, incomplete, or all when left empty.
  • Submission date range, on the date the submission was created.

The export runs in the background and arrives by email, as an attachment, at the address of the admin user who asked for it; the admin shows a notification when it has been sent. On a development environment the mailer's development redirect applies, so it may go to the development address instead. The file has a column each for created, completed and user (the email of the signed-in sender, or empty), then one column per ticked field. Multi-value answers are joined with ;. Two kinds of field do not survive the format: a File field reads Array, so get links to uploads through the API instead, and a Payment field joins its invoice id, amount and transaction id.

An export is a disclosure

The moment it is generated, the file is outside every control the platform has: in a mailbox, then a downloads folder, then perhaps a shared drive or a message to a colleague. Review each export as you would any disclosure of personal data, and set these rules with the client:

  • Tick only the fields the task needs. A mailing-list import needs an email and a consent answer, not the free-text message, the phone number or the date of birth. Untick sensitive answers (health, finances, identity documents) unless the task is about them.
  • Leave out internal values. Hidden fields filled by scripts (campaign codes, timezones, internal source tags) and the payment column are rarely wanted outside the business, and say more than they look.
  • The narrowest date range, and completed submissions only, unless the task is about abandonment.
  • Delete the file when the task is done, from the mailbox as well as the disk. The platform keeps no record of which fields were exported or why; record that yourself if the client has to answer for it.
  • Open it safely. The values are exactly what visitors typed, and the export does not neutralise spreadsheet formulas, so an answer beginning =, +, - or @ runs as a formula when the file is opened in a spreadsheet. Import it as text, or build the export from the API and prefix such values with an apostrophe, as the nightly CSV export does.

For a recurring export (a weekly list to a sales team, a nightly feed to another system), do not email a CSV at all. Read the API from the receiving system, as a backend user that can read submissions and nothing else, or push each submission as it completes (Pushing to a CRM).

Go deeper

Was this page helpful?

Your answer goes to the docs team.