How to submit a form with JavaScript (AJAX/JSON)
Use JavaScript submissions when you want to stay on the same page (SPAs), render your own success state, or submit JSON instead of a browser redirect.
Endpoint URL
Your endpoint URL is shown in the dashboard (and in the Integration tab). It looks like:
https://www.forms.fyi/api/f/<form_id>
Vanilla JavaScript (fetch)
This example reads a normal HTML form, converts it to JSON, and posts it to Forms.fyi.
const form = document.querySelector('#my-form');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
// Convert to JSON (skip honeypot field)
const data = {};
for (const [key, value] of formData.entries()) {
if (key !== 'special-instructions') data[key] = value;
}
const res = await fetch('https://www.forms.fyi/api/f/<form_id>', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const result = await res.json().catch(() => ({}));
if (res.ok) {
// TODO: show success UI
form.reset();
} else {
// TODO: show error UI
console.error(result.error || 'Submission failed');
}
});React-friendly example
Use a local status state and submit with fetch.
import { useState } from 'react';
export function ContactForm() {
const [status, setStatus] = useState('idle'); // idle | submitting | success | error
async function onSubmit(e) {
e.preventDefault();
setStatus('submitting');
const formData = new FormData(e.currentTarget);
const data = Object.fromEntries(formData.entries());
delete data['special-instructions']; // honeypot
try {
const res = await fetch('https://www.forms.fyi/api/f/<form_id>', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (res.ok) {
setStatus('success');
e.currentTarget.reset();
} else {
setStatus('error');
}
} catch {
setStatus('error');
}
}
if (status === 'success') return <p>Thanks! We got your message.</p>;
return (
<form onSubmit={onSubmit}>
<input type="text" name="special-instructions" style={{ display: 'none' }} tabIndex={-1} autoComplete="off" />
<input name="email" type="email" required />
<textarea name="message" required />
<button disabled={status === 'submitting'}>Send</button>
{status === 'error' ? <p>Failed to send. Try again.</p> : null}
</form>
);
}Common errors
- 403 Origin not allowed usually means your site domain doesn’t match the form’s allowed origin.
- 429 Too many submissions can be rate limiting or a monthly limit.
- Validation errors happen when the form has required fields or schema rules enabled (Pro).