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
Storing submissionsPage 9 of 20Updated
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:
Level
One per
Holds
Form submission
Visitor's go at the form
Its 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 submission
Step the visitor submitted
The step, and whether every answer on it was valid
Element submission
Input on a submitted step
The 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:
Element
Value in data
Text, email, phone, textarea, hidden, date, number, slider, radio, a single select, a single checkbox
A string (empty when not answered)
Checkbox with several options, a multi-select
A list of the chosen values
Range
A list of two numbers, lower first
File
A list with one object per file: type (image or file), id, name and link
Payment
An object with invoiceId, amount and transactionId (Taking payments)
Captcha
An 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:
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
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).