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

Submitting a form through the API

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

ToRequest
Submit a formPOST /cdna-api/forms/submission/{form id}
Continue a submission that came back incompletePUT /cdna-api/forms/submission/{token}
Ask whether a submission is completeGET /cdna-api/forms/submission/{token}/status
Read submissionsGET /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
Response 200 OK
{
  "result": "success",
  "data": {
    "id": 10,
    "activeRevision": {
      "id": 35,
      "steps": [
        {
          "name": null,
          "type": "normal",
          "position": 1,
          "elements": [
            {"name": "name", "type": "text"},
            {"name": "category", "type": "select"},
            {"name": "email", "type": "email"},
            {"name": "priority", "type": "select"},
            {"name": "message", "type": "textarea"}
          ]
        },
        {
          "name": "Thanks",
          "type": "result",
          "position": 2,
          "elements": [
            {"name": null, "type": "richtext"}
          ]
        }
      ]
    }
  }
}
Real response from this documentation site

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.

HTTP A 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"
}
ElementSend
Text, email, number, textarea, date, hiddenA string
Select, radio buttonsThe chosen option's value
Checkbox list, multi-selectA list of the chosen values
A single checkboxIts value when ticked; leave the key out when not
File uploadThe 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
CaptchaThe 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

StatusMeaningBody
200Stored and completeThe submission and a message
207Stored, but not complete: some answers failed the form's rules and are kept, marked invalidThe submission with its token, validationErrors by element name, and the message "The Form Submission has been saved successfully, but it contains validation errors."
422Nothing stored. The form requires a completed captcha, and it was missing or failedvalidationErrors listing every problem, the captcha's among them
202Stored, waiting for a payment. Not complete until the payment is confirmedredirectUrl to send the visitor to, or paypalDonateConfig with submissionToken (js-payment-element.js)
404No such form, or a form with no live revisionA 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:

HTTP Continue 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.

Go deeper

Was this page helpful?

Your answer goes to the docs team.