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
Submitting a form through the API
Storing submissionsPage 12 of 20Updated
A Core dna form can be filled from a React or Vue front end, a mobile app or another system, with no Core dna page involved. The three routes, the body they take, what each status means, how to continue a submission that came back incomplete, and why the request has to come from your server.
Who this is for. The headless developer whose front end is not served by Core dna, and the integrations engineer sending answers from another system. On a Core dna page, use the form layout and the platform's js-forms.js, which sends the same request for you (Multi-step forms).
The routes
To
Request
Submit a form
POST /cdna-api/forms/submission/{form id}
Continue a submission that came back incomplete
PUT /cdna-api/forms/submission/{token}
Ask whether a submission is complete
GET /cdna-api/forms/submission/{token}/status
Read submissions
GET /cdna-api/forms/submissions/{form id}, with admin credentials (Submissions)
The first three take no credentials and no CSRF token: they are built for visitors. Each answers on the site's own hostname, and the platform sends no cross-origin headers, so a browser on another origin cannot send the JSON request at all. Send it from your own server, which passes the visitor's answers on and the answer back. That server is also the place to keep the submission token (below).
Finding the form and its element names
Every answer is sent under its element's name. Read the form's steps and elements once, from your server with admin credentials, and build the front end's fields from them:
A form's steps and element names
GET /cdna-api/forms/form/10?properties={id,activeRevision{id,steps{name,type,position,elements{name,type}}}} HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Steps of type normal hold the questions; the result step is what to show afterwards. Elements without a name, such as rich text, are copy, not questions. The Form input reference lists every element type with the value it takes. A form's names change when an editor renames a field, and a submission is checked against the form's live revision, so read the definition again when a deploy or an edit could have changed it.
The body
One JSON object, keyed by element name, with every step's answers in one request: the API handles all the normal steps of the form at once.
HTTPA submission to the form above
POST /cdna-api/forms/submission/10
Content-Type: application/json
Accept: application/json
{
"name": "Alex Example",
"category": "content",
"email": "person@example.com",
"priority": "normal",
"message": "Hello from the API"
}
Element
Send
Text, email, number, textarea, date, hidden
A string
Select, radio buttons
The chosen option's value
Checkbox list, multi-select
A list of the chosen values
A single checkbox
Its value when ticked; leave the key out when not
File upload
The file as a data URL, data:image/png;base64,…. An upload that takes several files is named name[] and takes a list of them. A required upload is met only by a file part in a multipart/form-data request (below), not by a data URL
Captcha
The reCAPTCHA token, under the key g-recaptcha-response, whatever the element is called
Send a file as a file part to keep its name: a file sent as a data URL is stored as uploaded_file with an extension taken from its type instead. Where the name matters, send the form as multipart/form-data, with the file as a file part and a list as name[] once per value. Base64 also makes every file a third larger, which counts against the request size limit (File uploads).
What the status means
Status
Meaning
Body
200
Stored and complete
The submission and a message
207
Stored, but not complete: some answers failed the form's rules and are kept, marked invalid
The submission with its token, validationErrors by element name, and the message "The Form Submission has been saved successfully, but it contains validation errors."
422
Nothing stored. The form requires a completed captcha, and it was missing or failed
validationErrors listing every problem, the captcha's among them
202
Stored, waiting for a payment. Not complete until the payment is confirmed
redirectUrl to send the visitor to, or paypalDonateConfig with submissionToken (js-payment-element.js)
404
No such form, or a form with no live revision
A message
A 207 is a success code that means the visitor is not done. A client that checks only for 2xx tells the visitor "Thank you" for a submission nobody will act on, since anything that counts or acts on submissions looks at complete ones (Submissions). Treat 200 as done, 202 as "go and pay", and everything else as "show the errors". The keys of validationErrors are element names, so they map straight back to your fields (Responses and errors). For the same reason, a job that acts on submissions should run on a submission's Completed event: a 207 stores a new submission too.
Continuing after a 207
Correct the answers and send them to the submission's token, not to the form:
HTTPContinue the stored submission
PUT /cdna-api/forms/submission/{token}
Content-Type: application/json
{ …every answer again, corrected… }
Send every answer again, not only the corrected ones. The platform handles the whole form again, and an answer left out clears the one stored.
The answers on a continuation are 200 when complete, 422 with the errors when still not (never 207: the submission already exists), 202 for a payment, and 409 when the submission can no longer change: "The form submission has already been completed and cannot be changed anymore", or "The form has been changed and the submission cannot be changed anymore" when an editor published a new revision of the form in between. On a 409 for a changed form, start again with a POST.
The token is the only credential. Anyone holding it can change the submission until it is complete. Keep it on your server, tied to the visitor's session; never put it in a URL or an email. A submission made by a signed-in member can only be continued in that member's session.
Continuing keeps one submission per visitor. Posting again instead leaves the first one behind as an incomplete submission.
Asking for the status
GET /cdna-api/forms/submission/{token}/status answers {"completed": true} or {"completed": false}, with no result around it, and a 404 for a token it does not know. Its use is after a 202: the payment is confirmed by the provider's call to the site, not by your request, so poll until it says true, and give up after a while with a message that the payment is being confirmed. The platform's own script does this for PayPal Donate, asking every two seconds, thirty times (js-payment-element.js).
Sending it once
A POST that timed out may have been stored, and sending it again stores a second submission (Retries). Nothing in the answer to a lost request can be looked up afterwards. Where a duplicate would matter, add a Hidden element to the form, fill it with an id your system makes for each attempt, and remove duplicates where submissions are consumed: in the job that acts on them, or in the CRM.