Hook the submit event
Listen for 'submit' on the form, call e.preventDefault(), then send a fetch() POST with the FormData. splitforms returns JSON, so you can branch on success/error.
Form template · Basic contact form
A JavaScript contact form that submits in the background, shows success inline, and never reloads the page. ~30 lines of vanilla code.
This template is one of our free HTML form templates. See more form templates by industry.
Overview
Building a contact form in plain JavaScript is the same three moves every time: listen for the submit event, call preventDefault() so the page never reloads, package the fields with new FormData(form), and fire a fetch() POST. The one piece vanilla JS can't give you is a server on the other end to receive that message, store it, and email you. SplitForms is that missing endpoint. Point your fetch() at https://splitforms.com/api/submit with a hidden access_key field, await the response, and swap in your "Thanks, we'll be in touch" line — no Node, Express, or PHP, and it runs the same from a static host like GitHub Pages.
Every template is free and fully editable in the drag-drop builder — swap fields, colors, and the button label before you publish.
No server, API route, or SDK — your JavaScript Contact Form posts straight to one endpoint.
Every submission is emailed to you and saved to a searchable dashboard — spam filtered before it reaches you.
It's plain HTML you own — paste the JavaScript Contact Form snippet, swap in your access key, and ship.
Copy and ship
HTML by default — the same field set also renders as React, Next.js, PHP, and cURL in your dashboard. Every variant posts to the same /api/submit endpoint. Replace YOUR_ACCESS_KEY with the key from your dashboard; that's the only edit.
<form action="https://splitforms.com/api/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
<input type="hidden" name="subject" value="New contact form submission">
<label for="name">Full name *</label>
<input id="name" type="text" name="name" placeholder="Jane Builder" required>
<label for="email">Email *</label>
<input id="email" type="email" name="email" placeholder="jane@example.com" required>
<label for="phone">Phone *</label>
<input id="phone" type="tel" name="phone" placeholder="+1 415 555 0142" required>
<label for="message">Message *</label>
<textarea id="message" name="message" placeholder="How can we help?" required></textarea>
<!-- honeypot — bots fill every field -->
<input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off">
<button type="submit">Send</button>
</form><p class="splitforms-attribution" style="margin-top:12px;font-size:11px;color:#888;text-align:right">
Powered by <a href="https://splitforms.com" style="color:#888;text-decoration:none" target="_blank" rel="noopener">splitforms</a>
</p>
<!-- drop-off analytics + partial leads (the form works without it) -->
<script src="https://splitforms.com/embed.js" async></script>How to add it
To add a JavaScript Contact Form to your site you need three things: a free splitforms access key, the HTML snippet above, and your key pasted into it. No backend, database, or CAPTCHA library — the form posts to one URL and every submission lands in your inbox and dashboard.
Listen for 'submit' on the form, call e.preventDefault(), then send a fetch() POST with the FormData. splitforms returns JSON, so you can branch on success/error.
On success, hide the form and show a thank-you message. On error, surface the error message inline so the user can retry without losing their input.
You already get HTML5 required attributes for free. Add custom validation in the submit handler if you want regex-based email checks or business-logic gates.
What's included
The traditional HTML form posts and reloads the page — you lose scroll position, animations replay, and the user gets the jarring 'Thanks!' page. AJAX submission with the Fetch API is two extra event listeners — one to preventDefault on submit and one to render the success state. We give you the snippet.

Where submissions go
Reply-to is wired to the visitor's email, so one tap on Reply answers them directly — while the built-in honeypot and automatic spam scoring filter out bots before they reach you. Every submission is emailed to your inbox and can forward to a webhook or Slack the moment a visitor hits send.

How SplitForms works
splitforms receives the submission, filters spam, saves it to your dashboard and emails you — then, on paid plans, sends it on to your webhooks and integrations. No backend infrastructure to build.

Point your form to your unique SplitForms endpoint. That's it.

We instantly capture and organize every submission in your inbox.

Send data to email, spreadsheets, CRMs, webhooks, and 7,000+ apps.

No credit card required. Set up in under 60 seconds.
Connect & automate
On paid plans, submissions can also go to Slack, Discord, Google Sheets, Notion, Airtable, Zapier or any signed webhook — and splitforms works on the platforms you build on, from WordPress and Shopify to Next.js. With Stripe connected, your forms can take payments too.

Questions
Reach for new FormData(form). It builds the exact multipart request a plain HTML <form> submit would send, which is what /api/submit is built to receive, so the hidden access_key input and every other field are picked up automatically and the browser sets the Content-Type and boundary for you. Add a field to the HTML later and your JS doesn't change. Hand-rolling a JSON body means listing each field by name and re-editing that object every time the form changes, for no gain here.
Add form.addEventListener('submit', async (e) => { e.preventDefault(); ... }). preventDefault() cancels the browser's built-in full-page POST, so your own fetch() to https://splitforms.com/api/submit runs in the background and the visitor stays on the page while you render the result. Skip it and the browser navigates away before your success message ever paints.
In your SplitForms dashboard. Every successful fetch to /api/submit is stored as a submission you can read, search, and export to CSV (paid plans) — no database or mail server of your own to run. A notification email also fires to you on each new submission, and that email is free on every plan, including the free tier's 500 total submissions. Your JavaScript only owns the POST and the on-page confirmation; receiving, storing, and alerting happen at SplitForms.
Neither. The access_key is a public form identifier from your dashboard, not a secret — it's meant to ship in client-side markup, so there's nothing to hide behind a proxy. And the endpoint accepts cross-origin POSTs straight from the browser, so a fetch() from your static site, a GitHub Pages URL, or even a CodePen reaches it directly with no backend of your own.
No. fetch() and FormData are available in every browser since 2017. The example is ~30 lines of vanilla JavaScript with no dependencies.
splitforms returns JSON: { success: true, message: 'Submission received.' } or on error a 4xx/5xx with { success: false, message: '...' }. Branch on response.json().
For text-only forms it's instant — no progress bar needed. For file uploads, fetch supports XHR-based progress events; we ship a Pro example with an upload progress bar.
Simple pricing
Free gives you 500 submissions for $0 — no card, no time limit. Pro is $5/month for 5,000 submissions a month, and Annual is $49/year for 15,000 a month with every Pro feature.
For side projects and indie devs.
Every paid feature, for agencies and growing products.
Every Pro feature with triple the volume — $0.27 per 1,000 at full use.
No credit card required on Free • Cancel anytime • Full pricing and FAQ