Quickstart
Posting a form to ezform takes one URL and one hidden field. There is no SDK to install, no API key, and nothing to import: a plain HTML form works, and so does fetch. This guide sets up a working form and explains the two pieces of per-form configuration you need from the console.
Create a form in the console first. It gives you a form ID, which goes in the URL, and the name of a honeypot field, which goes in your markup. Both are per-form and neither is a secret.
Allow your site’s origin
Before anything will be stored, add the origin your form is served from to the form’s allowlist: https://example.com, scheme and host, no path. A submission from an origin that is not on the list is discarded without being written, which is what keeps someone who has read your form ID out of your inbox.
This is the single most common reason a new form appears to do nothing. It is also, deliberately, indistinguishable from success in the HTTP response: see Responses and failure.
Add the form
The honeypot is a text input that people never see and bots fill in. Give it the field name the console generated. Do not rename it, and do not use display: none: off-screen with aria-hidden and tabindex="-1" keeps it away from keyboards and screen readers while leaving it visible to the scripts that matter.
A working form
<form action="https://api.ezform.io/f/YOUR_FORM_ID" method="POST">
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<!-- The honeypot. Use the field name from the console. -->
<div style="position:absolute;left:-9999px" aria-hidden="true">
<label for="YOUR_HONEYPOT_FIELD">Company website</label>
<input
id="YOUR_HONEYPOT_FIELD"
name="YOUR_HONEYPOT_FIELD"
type="text"
tabindex="-1"
autocomplete="off"
/>
</div>
<button type="submit">Send</button>
</form>async function onSubmit(event) {
event.preventDefault()
const fields = Object.fromEntries(new FormData(event.currentTarget))
const response = await fetch('https://api.ezform.io/f/YOUR_FORM_ID', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(fields),
})
// A non-2xx here means the request never reached the handler, which for a
// browser means CORS. See "Responses and failure".
if (!response.ok) throw new Error(String(response.status))
}---
const action = `https://api.ezform.io/f/${import.meta.env.PUBLIC_FORM_ID}`
const honeypotField = import.meta.env.PUBLIC_HONEYPOT_FIELD
---
<form action={action} method="POST">
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<div class="absolute left-[-9999px]" aria-hidden="true">
<label for={honeypotField}>Company website</label>
<input id={honeypotField} name={honeypotField} type="text" tabindex="-1" autocomplete="off" />
</div>
<button type="submit">Send</button>
</form>curl https://api.ezform.io/f/YOUR_FORM_ID \
-H "content-type: application/json" \
-H "origin: https://example.com" \
-d '{"email":"someone@example.com","message":"Hello"}'The curl example sends an Origin header by hand. It has to: the origin allowlist is checked on every request, and a request that carries no origin at all is discarded like any other unattributable one.
What happens next
The submission is written to the database before anything else can happen to it, and only then queued for delivery. That order is the whole product: a delivery that fails is retried, and a delivery provider having a bad morning cannot lose a lead that is already stored.
You will get an email at each of the form’s verified destinations. If nothing arrives, the submission is still in the console.
Where to go next
- Your stack, if it is one of Astro, Next.js, Hugo, Jekyll, Eleventy or Webflow
- Submitting a form for what the endpoint accepts and what it sends back
- Responses and failure for why every outcome is a 2xx
- Spam and origins for what the honeypot and the allowlist each do