Skip to main content
The form keeps its action and method, so it posts on its own until the script runs. The same request carries files, a spam blocker token and validation errors, and Axios or XMLHttpRequest can send it for code that uses them.

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.

Fetch

To send a form with fetch, keep action and method="POST" on the form, post a FormData built from it on submit, and send Accept: application/json:
Until the script has run, the browser posts the form itself and the visitor lands on the thank you page. After that, the answer is JSON. The email field is the visitor’s address, so it becomes the reply to address of your notification email and the recipient of the auto response. A stored submission answers with code: 200. A refused one answers with the reason in message, see What every form needs. Send Accept: application/json rather than leaving it out, otherwise the answer is the thank you page’s HTML and res.json() throws. To refuse a second click before the answer arrives, disable the submit button and enable it again after:
A second submission within 15 seconds from the same address gets a 429, so the button stays disabled until the answer is in. fetch rejects only when no answer came back; a refused submission resolves, with the reason in data.message. To post an object instead, send it as JSON with Content-Type: application/json:
The keys are the field names formcarry stores.

Files

To send files, add file inputs to the form from the first example and keep the FormData post:
The script does not change. A multiple input sends every chosen file under its name. For File objects from elsewhere, append each one under its own name:
Leave Content-Type to the browser rather than setting it, otherwise the multipart boundary is missing and the upload fails. For small files, stay with JSON and send each file as a data URL, one key per file, 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.

Spam blocker

To add a challenge, load its script, put a div with the widget class and your site key inside the form, and paste the secret key into the form’s settings under Form Security: With reCAPTCHA v2:
The reCAPTCHA v2, hCaptcha and Turnstile widgets each add a field named after their token to the form, so the FormData post above carries it. With reCAPTCHA v3 there is no widget. Load the script with your site key, add a hidden input for the token, and ask for a token when the visitor submits:
With a JSON post, read the token and add it by hand:
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.

Validation errors

A 422 carries errors, one entry per failing field, each with a message. To mark each failing field invalid and let the browser report it:
The browser shows each message next to its field in its own style. Clear the custom validity on the next input rather than leaving it set, otherwise the browser refuses to submit the form again. To show each message in your own markup instead, mark the field and add a span after it:
Remove the class and the spans before the next post rather than leaving them, otherwise old messages stay next to fields that now pass.

Axios

The same post with Axios from its CDN script, with form and result from the first example:
Leave Content-Type to Axios rather than setting it, otherwise the FormData upload loses its boundary. Axios rejects on any status outside 2xx, so formcarry’s answer to a refused submission is in err.response.data. A request that got no answer has no response, so check for it before reading it. To post an object instead, pass it in place of the FormData; Axios sends it as JSON:

XMLHttpRequest

For code that cannot use fetch, the same post with XMLHttpRequest:
onload runs for 422 and 403 too, so read data.code. onerror runs only when no answer came back. For a JSON body, set Content-Type and send a string:

What’s next

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