This is the form your visitors will see
Clean, fast, no CAPTCHA. Try it — this is a real working demo.
What happens next ⚡
- 📥Submission received — stored in your searchable dashboard
- 📧Email notification — delivered to your inbox instantly
- 🛡️Spam filtered — AI classifier + honeypot, no CAPTCHA
- 🔗Webhook fired — optional: forward to Slack, Discord, Sheets
- ↩️User redirected — to your thank-you page
500 submissions/month free · No credit card
What is the form action attribute?
The action attribute on an HTML <form> element specifies the URL where the form data should be sent when the user clicks the submit button. Without it, the form submits to the current page URL — which is almost never what you want for a contact form.
Here's the simplest working example:
<form action="https://splitforms.com/api/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
<label>Name <input type="text" name="name" required></label>
<label>Email <input type="email" name="email" required></label>
<label>Message <textarea name="message" required></textarea></label>
<button type="submit">Send</button>
</form>When the visitor clicks Send, the browser collects all named fields, encodes them as application/x-www-form-urlencoded (the default content type for POST forms), and sends an HTTP POST request to the URL in action. The server at that URL receives the data and does something with it — saves it, emails it, stores it in a database.
POST vs GET: which method should you use?
The method attribute works alongside action. It controls the HTTP verb the browser uses to send the data. Two practical choices:
- POST — sends data in the request body. Not visible in the URL. No practical size limit. Required for file uploads. This is what you want for contact forms, lead capture, and any form with sensitive data.
- GET — appends data to the URL as query parameters (e.g.
?name=John&email=john@example.com). Visible in browser history and server logs. Capped at roughly 2,000 characters in most browsers. Useful only for search forms and filtering.
Rule of thumb: if your form has a textarea, an email field, or any data you wouldn't want in a URL — use POST.
<!-- POST: data in request body (use this for contact forms) -->
<form action="https://splitforms.com/api/submit" method="POST">
<!-- GET: data in URL query params (search forms only) -->
<form action="/search" method="GET">Relative vs absolute action URLs
The action attribute accepts both relative and absolute URLs. The choice matters:
- Absolute URL (
https://splitforms.com/api/submit) — the browser sends the request to that exact URL regardless of where the form lives. This is what you use with a hosted form backend. - Relative URL (
/api/submit) — the browser resolves it against the current page origin. If your site ishttps://mysite.comand action is/api/submit, the request goes tohttps://mysite.com/api/submit. This is what you use with a same-origin API route. - Root-relative URL (
/api/submit) — same as relative but always starts from the domain root.
For static sites hosted on Vercel, Netlify, Cloudflare Pages, GitHub Pages, or S3, you almost always want an absolute URL pointing to a hosted form backend — because there's no server on your origin to handle the POST request.
Form content types: urlencoded, multipart, and JSON
The enctype attribute controls how the browser encodes form data before sending. Three options:
<!-- Default: application/x-www-form-urlencoded -->
<form action="/api/submit" method="POST">
<!-- works everywhere, no enctype needed -->
<!-- File uploads: multipart/form-data (REQUIRED for file inputs) -->
<form action="/api/submit" method="POST" enctype="multipart/form-data">
<input type="file" name="attachment">
<!-- JSON: text/plain (hack, not recommended — use fetch instead) -->
<!-- Most servers don't parse this correctly from a native form -->For contact forms without file uploads, the default application/x-www-form-urlencoded is correct. Every form backend and server framework parses it automatically. Only switch to multipart/form-data when you have a <input type="file"> element.
If you need to send JSON (e.g. your API only accepts application/json), you can't do it with a native form — you need JavaScript with fetch(). See the section below.
Using fetch() instead of the action attribute
The action attribute triggers a full page navigation on submit. For a smoother UX (no page reload, inline success/error messages), intercept the submit event and send the data with fetch():
<form id="contact-form">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
<input type="text" name="name" required>
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
<script>
const form = document.getElementById('contact-form');
form.addEventListener('submit', async (e) => {
e.preventDefault(); // stop the page reload
const formData = new FormData(form);
const response = await fetch('https://splitforms.com/api/submit', {
method: 'POST',
body: formData,
});
const result = await response.json();
if (result.success) {
form.reset();
alert('Message sent!');
} else {
alert('Something went wrong: ' + result.message);
}
});
</script>Key points: FormData automatically collects all named fields, e.preventDefault() stops the native form submission, and fetch with FormData sets the correct content type (multipart/form-data) automatically. Always keep the action attribute on the form as a fallback in case JavaScript fails — progressive enhancement.
Using splitforms as your form action endpoint
Instead of building a backend, deploying it, maintaining SMTP, and dealing with spam — point your form action at https://splitforms.com/api/submit and you're done. Here's what happens on submit:
- The browser POSTs form data to the splitforms endpoint.
- Splitforms validates your access key and stores the submission.
- You get an email notification at the address configured in your dashboard.
- AI spam filtering runs automatically — no CAPTCHA needed.
- The visitor is redirected to your thank-you page (configurable).
<form action="https://splitforms.com/api/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
<input type="hidden" name="redirect" value="https://yoursite.com/thanks">
<input type="text" name="name" placeholder="Name" required>
<input type="email" name="email" placeholder="Email" required>
<textarea name="message" placeholder="Message" required></textarea>
<!-- Honeypot spam protection -->
<input type="checkbox" name="botcheck" class="hidden" tabindex="-1" autocomplete="off">
<button type="submit">Send Message</button>
</form>Plans: free up to 500 submissions/month, Starter at $1/month for webhooks, Pro at $5/month for 5,000 submissions plus custom SMTP. Email hello@splitforms.com with questions, or get your free access key.
For framework-specific guides, see Next.js, React, Astro, and Svelte integration pages.
Common mistakes and how to fix them
- Missing
nameattribute on inputs. The browser only sends fields that have aname. An input withoutnameis silently dropped. Always addname="email", not justid="email". - Forgetting
method="POST". The default method is GET, which appends data to the URL. For contact forms, always setmethod="POST"explicitly. - Wrong content type for file uploads. If you have
<input type="file">, you must setenctype="multipart/form-data"or the file won't be sent. - CORS errors with fetch. If you're using
fetch()to submit to a cross-origin endpoint, the server must includeAccess-Control-Allow-Originin its response. Native form submissions (withaction) don't trigger CORS — onlyfetch()does. - Submit button outside the form. The
<button type="submit">must be inside the<form>element. If it's outside, the form won't submit.
For more debugging tips, see contact form not working and CORS error fix guide.
FAQ
What does the form action attribute do?
The action attribute specifies the URL where the form data is sent when the user clicks submit. If you omit action entirely, the form submits to the current page URL — which is rarely what you want for a contact form. Set action to a server endpoint (or a hosted form backend like splitforms.com/api/submit) that can process the submission and send you an email notification.
Should I use GET or POST for my form action?
Use POST for any form that sends meaningful data — contact forms, lead capture, file uploads, anything with an email address. GET appends data to the URL as query parameters, which is visible in browser history, logged by analytics tools, and capped at roughly 2,000 characters. POST sends data in the request body, supports file uploads, and has no practical size limit.
Can I use a relative URL for the form action?
Yes. A relative URL like action="/api/submit" resolves against the current page's origin. If your site is at https://example.com and the form action is "/api/submit", the browser sends the request to https://example.com/api/submit. This is useful when you have a same-origin API route (e.g. a Next.js Server Action or Express endpoint). For cross-origin endpoints, use the full absolute URL.
What happens if I leave out the action attribute?
If action is omitted, the form submits to the current URL. This means the page reloads with the form data appended (for GET) or sent in the body (for POST), but unless your server has logic to handle that POST request, nothing happens — the page just reloads with the form still visible. Always set action explicitly so submissions go somewhere useful.
Can I submit a form with JavaScript instead of using the action attribute?
Yes. You can intercept the submit event with JavaScript, call event.preventDefault(), and send the data yourself with fetch(). This gives you control over the UI (loading states, success messages, no page reload). The action attribute still works as a fallback — if JavaScript fails to load, the native form submission fires and the data still reaches your endpoint. This progressive enhancement pattern is the most resilient approach.
More reads: HTML contact form code, all blog posts, and the splitforms docs.