Skip to main content
It builds a FormData from the form, posts it and shows formcarry’s answer, and the same request carries files, a spam blocker token and validation errors.

Prerequisites

Before you start, you need:
  • A formcarry account. Sign up is free.
  • A form in the dashboard. Its endpoint is on the form’s Setup page. The examples use https://formcarry.com/s/AbC123xyz; put yours in its place.

The request

To send a form with jQuery, build a FormData from the form element, post it with $.ajax, and send Accept: application/json so the answer comes back as JSON:
Every field needs a name, because FormData reads the fields from the elements and formcarry stores each value under its name. The email field is the visitor’s address, so it becomes the reply to address of your notification and the recipient of the auto response. Set processData: false and contentType: false rather than leaving the defaults, otherwise jQuery tries to serialise the FormData itself and the request fails before it is sent. A stored submission answers with code: 200. jQuery routes every status outside 2xx to error, so a refused submission’s answer is in xhr.responseJSON, with the reason in message, see What every form needs. Without a file input, $(this).serialize() posts the same fields as application/x-www-form-urlencoded, with no processData or contentType to set:
serialize() leaves file inputs out, so a form that uploads keeps the FormData request.

JSON

To send JSON instead, collect the fields into an object, JSON.stringify it, and set contentType: "application/json":
The keys are the field names formcarry stores.

Files

A FormData built from the form carries its file inputs, so the first request uploads them as is. To take one file in one field and several in another, add the inputs to the form; the same $.ajax options send them:
Keep contentType: false rather than setting multipart/form-data yourself, otherwise the multipart boundary is missing and the upload fails. For File objects from elsewhere, such as a Dropzone with its own upload turned off, append each one under its own name:
autoProcessQueue: false keeps Dropzone from posting to url itself, and getAcceptedFiles() returns what the visitor dropped. For a small file inside the JSON request, read it as a data URL and give it its own key, never an array:
Use data URLs for small files. Using them with larger files bloats the request by about a third and reaches the 50 MB limit sooner.
Files are stored on paid plans only. Free plans store the rest of the submission without them.

Validation errors

A 422 carries errors, one entry per failing field, each with a message. To show each one next to its field, and every other refusal in an alert:
Read the 422 in error rather than in success, otherwise the handler never runs. To clear the marks before the next attempt, remove them at the top of the submit handler:
Clear them on every submit rather than only on success, otherwise old messages stay next to fields that now pass.

Spam blocker

To add a challenge, load the vendor’s script, put a div with the widget class and data-sitekey inside the form, and paste the secret key into the form’s settings under Form Security: With reCAPTCHA v2:
The widget adds a field named g-recaptcha-response to the form, so the FormData request carries the token as is, and Object.fromEntries puts it into the JSON request too. With reCAPTCHA v3 there is no widget: call grecaptcha.execute("SITE_KEY", { action: "submit" }) when the visitor submits and put what it returns into a hidden input named g-recaptcha-response. To hold the request until the visitor has passed the challenge, check the token at the top of the submit handler:
Refuse to send while the token is empty rather than posting anyway, otherwise formcarry answers 403 for reCAPTCHA or 400 for the others. Add localhost to the challenge’s allowed domains while you test.

The submit button

To stop a second submit while the request runs, disable the button before $.ajax and enable it again in complete:
complete runs after success and error alike, so the button comes back after a refused submission too. Disable the button rather than trusting one click, otherwise the second click gets a 429: 1 submission per 15 seconds per form per IP address. To show that the request is running, swap the button text for a spinner and put the text back in complete:
Put the text back in complete rather than in success, otherwise a refused submission leaves the spinner in the button.

Reset

To clear the fields after a stored submission, reset the form in success:
Reset in success rather than in complete, otherwise a refused submission clears what the visitor typed.

What’s next

Stuck? Write to help@formcarry.com. Include the form id.