HTML form submission6 min read
The HTML form action attribute, explained
What a form's action attribute does, how relative and empty values resolve, how formaction sends one form to two places, and why static hosts answer 405.
The action attribute is the URL a form sends its data to. A relative value resolves against the page's address, an empty or missing one means the page itself, and a submit button's formaction overrides it for that button. On a static site, set it to the full URL of a form backend such as https://ikiform.com/f/<formId>, because your own host can't take the POST.
Most broken forms on static sites are broken in this one attribute. The markup looks fine, the button works, and the browser shows a 405 or a blank page, because the data went somewhere that can't receive it. This page shows exactly where a given action sends data, then uses formaction to route one form to two inboxes.
What's different here
action is a URL like any href, with one twist: a form with no action doesn't do nothing, it posts to the page it's on. On a server-rendered site that page might handle the post. On a static host it can't, so a missing or relative action fails only after you deploy.
Where each value sends the data
Take a page at https://example.com/company/contact.html and five forms on it:
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
<form action="/api/contact" method="post">
<form action="send" method="post">
<form action="" method="post">
<form method="post">| action | Resolves to | On a static host |
|---|---|---|
https://ikiform.com/f/frm_... | Exactly that URL | Works. Ikiform takes the POST |
/api/contact | https://example.com/api/contact | Only works if your host runs a function at that path |
send | https://example.com/company/send, relative to the folder | 405 or 404 |
"" (empty) | The page itself, contact.html | 405 on most static hosts |
| Missing | The page itself | 405 on most static hosts |
- An absolute action is used exactly as written, so the post reaches Ikiform, which answers with a redirect.
- A relative, empty or missing action is resolved against the page's own address, or against a base element if the page has one.
- The post then goes to your own host. A static host serves files only, so it refuses the POST, usually with 405.
<base href> element in the page's head changes how every relative URL resolves, including action. Some site builders and single-page app setups add one. Use a full URL and it can't matter.Check what the browser will use
In the browser console, the attribute and the property differ. getAttribute returns what you wrote; the action property returns the resolved URL the browser will actually post to:
const form = document.querySelector("form");
form.getAttribute("action");
form.action;If form.action shows your own domain, that's where the data goes. Builders such as Webflow and Framer often set action from their own settings panel, so check the published page, not your source. The site builder guides say where each builder keeps it.
One form, two inboxes
A submit button's formaction replaces the form's action for that button only. Here, one set of fields goes to two Ikiform forms. Ask support posts to the support form, Talk to sales to the sales form, and each Ikiform form emails its own recipients:
<form action="https://ikiform.com/f/frm_supportxxxxxxxxx" method="post">
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" required />
<label for="message">How can we help?</label>
<textarea id="message" name="message" rows="5" required></textarea>
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
<button>Ask support</button>
<button formaction="https://ikiform.com/f/frm_salesxxxxxxxxxxx">Talk to sales</button>
</form>- Each Ikiform form has its own notification recipients, spam settings and webhooks, so support and sales never see each other's mail.
- Both forms need the same field definition, or none. With fields defined, a field one form doesn't know is refused with
422. - The other
form*attributes work the same way:formmethod,formenctype,formnovalidateandformtarget. A Save draft button withformnovalidateskips the browser's checks. formactiononly works on submit buttons:<button>,<input type="submit">and<input type="image">.
The alternative is one Ikiform form with a topic field, and routing by topic in a webhook or your inbox filters. That keeps one list and one export. Two forms are simpler when the two teams shouldn't share recipients at all.
action with a query string
With method="get", the browser replaces the action's query string with the form's fields. The lang=de in the first form below is silently dropped; the second keeps it as a hidden input:
<form action="/search?lang=de" method="get">
<input name="q" />
</form>
<form action="/search" method="get">
<input name="q" />
<input type="hidden" name="lang" value="de" />
</form>With method="post" the query string is kept, because the fields go in the body. That doesn't change anything for Ikiform: query parameters on the endpoint URL are not stored, so use hidden inputs for values you want kept.
Inputs outside the form
Layouts sometimes need an input that isn't inside the <form> element, such as a footer signup next to other content. The form attribute on an input or button joins it to a form by id, and it posts to that form's action:
<form id="newsletter" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post"></form>
<footer>
<input form="newsletter" name="email" type="email" required />
<button form="newsletter">Subscribe</button>
</footer>mailto: and javascript: actions
action="mailto:you@example.com" asks the visitor's mail app to open a message with the fields. Nothing is sent unless they have a mail app set up and press send, and you never learn that they tried. action="javascript:..." runs code instead of submitting and is blocked by most content security policies. Both are worth replacing with a real endpoint.
Check it works
- Under the form's Allowed domains, add your site's domain, plus
localhostwhile you develop. Leave it empty to accept posts from anywhere. - Open the published page, not your source, and run
document.querySelector("form").actionin the console. It prints your full Ikiform URL. - Submit the form. The Network panel shows a POST to
ikiform.comanswered with303, not a POST to your own domain. - In the two-inbox form, submit once with each button. Each submission appears under its own form in the dashboard.
- Check the inbox of each notification recipient. Each gets an email with the fields, and Reply goes to the submitter when the form has an
emailfield.
Gotchas
| Problem | Cause and fix |
|---|---|
405 Method Not Allowed from your own domain | The action is relative, empty or missing, so the post went to your static host. Use the full Ikiform URL |
404 not_found from ikiform.com | The form id in the action is wrong, or the form was deleted. Copy the endpoint from the form's page in the dashboard |
| The builder keeps resetting the action | Some builders own the action of their form block. Use their custom action setting, or an embed with your own markup |
| The browser warns that the form is not secure | The action uses http: on an https: page. Ikiform's endpoint is https: only; fix the scheme |
| Fields from a second form are posted too | Forms can't be nested. The browser closes the outer form at the inner <form> tag. Use the form attribute instead |
For the full submission flow, from the click to the redirect, see the overview. Everything the endpoint accepts is in HTML forms.
Questions
It submits to the page it's on. That works when your server handles posts at that URL, but on a static host it fails, usually with 405 Method Not Allowed.
action is set on the form and applies to every submit. formaction is set on one submit button and overrides action when that button is used, so one form can send to two places.
Yes. Browsers post cross-site with no special setup, which is how a static page posts to a form backend. The receiving server decides whether to accept it; Ikiform checks the page's origin against the form's allowed domains.
With method get, the browser replaces the action's query string with the form's own fields. Move each parameter into a hidden input. With method post the query string is kept.
Stuck on a step, or want a guide for your situation? Email hello@ikiform.com.