JavaScript and fetch
Submit to Nisuform with fetch to keep visitors on the page, then read the JSON response to show success or an error.
Send your fields with fetch when you want to handle the submit yourself. Requests that do not ask for text/html get a JSON response instead of a thank-you page.
const response = await fetch('https://api.nisuform.com/s/YOUR_FORM_KEY', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({ name: 'Jane Doe', email: 'jane@example.com', message: 'Hello!' }),
})
const result = await response.json()
if (!result.ok) {
throw new Error(result.message)
}FormData is sent as multipart/form-data, which is also how you send files. In that case, don't set the Content-Type header yourself. The browser adds it with the right boundary.
Responses
A stored submission answers 200 with its ID:
{ "ok": true, "submissionId": "3f2c8a61-5d4e-4b7a-9c1f-2a6e8d0b4c17" }When your team has used 95% of its monthly submissions, the response also carries a warning. The submission is still stored:
{ "ok": true, "submissionId": "3f2c8a61-5d4e-4b7a-9c1f-2a6e8d0b4c17", "warning": "near_limit" }A submission caught by the honeypot, the fill-time check or your blocked IPs and countries also answers 200, but without a submissionId. Bots can't tell they were filtered.
{ "ok": true }A rejected submission answers with a matching HTTP status and an error code you can branch on:
{ "ok": false, "error": "rate_limited", "message": "Too many requests. Try again shortly" }The message is written for people, so you can show it to the visitor as is. Every code is listed in Errors.
Handle errors
async function submit(data) {
const response = await fetch('https://api.nisuform.com/s/YOUR_FORM_KEY', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify(data),
})
const result = await response.json()
if (result.ok) {
return { sent: true }
}
if (result.error === 'rate_limited') {
return { sent: false, message: 'Please wait a minute and try again.' }
}
return { sent: false, message: result.message }
}Send the fill time
Browsers that submit a form faster than a person could are almost always bots. Record when the form was shown and send the difference as _elapsed, in milliseconds. Submissions faster than 2 seconds are dropped.
const shownAt = Date.now()
form.addEventListener('submit', async (event) => {
event.preventDefault()
const data = new FormData(form)
data.set('_elapsed', String(Date.now() - shownAt))
await fetch(form.action, { method: 'POST', headers: { Accept: 'application/json' }, body: data })
})The styled embed from the form builder already does this for you.
How values are saved
- Every value is saved as text.
- JSON numbers and booleans become text, like
"3"and"true". - JSON arrays of plain values are joined with a comma, like
"Design, Development". - Nested objects are saved as JSON text.
- A JSON body must be an object. Anything else is treated as an empty submission.
CORS
The endpoint sends Access-Control-Allow-Origin: * and answers preflight requests, so fetch works from any website. Cookies and credentials are not needed, so don't send them.
Only the Content-Type and Accept request headers are allowed. Adding other headers, like Authorization, makes the browser's preflight check fail. The response's Retry-After header can't be read from browser JavaScript, so wait a minute after a 429 before retrying. To accept submissions only from your own sites, set allowed websites.
Submitting from a server
You can post to the endpoint from your own backend, a serverless function or a script. Keep in mind:
- Requests from a server usually have no
OriginorRefererheader, so allowed websites do not apply to them. - Rate limits are per IP address. Every request from your server shares the server's IP, so a busy relay can hit the limit of 30 requests a minute per IP, and 5 a minute per form. Post from the visitor's browser when you can.
- Nisuform records your server's IP address, user agent and location instead of the visitor's.