HTML form submission

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.

Preet SutharFounder, Ikiform

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:

contact.html
<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">
actionResolves toOn a static host
https://ikiform.com/f/frm_...Exactly that URLWorks. Ikiform takes the POST
/api/contacthttps://example.com/api/contactOnly works if your host runs a function at that path
sendhttps://example.com/company/send, relative to the folder405 or 404
"" (empty)The page itself, contact.html405 on most static hosts
MissingThe page itself405 on most static hosts
How the browser turns a form's action attribute into the URL it posts toabsoluterelative, empty or missingaction="https://ikiform.com/f/..."used as writtenIkiform answers303action="send" ornoneresolved againstthe pageor a <base href> if thepage has oneyour static hostcan't take POST: 405How the browser turns a form's action attribute into the URL it posts toabsoluterelative, empty or missingaction="https://ikiform.com/f/..."used as writtenIkiform answers 303action="send" or noneresolved against the pageor a <base href> if the page hasoneyour static hostcan't take POST: 405
  1. An absolute action is used exactly as written, so the post reaches Ikiform, which answers with a redirect.
  2. A relative, empty or missing action is resolved against the page's own address, or against a base element if the page has one.
  3. The post then goes to your own host. A static host serves files only, so it refuses the POST, usually with 405.
If the address bar shows your own domain right after submitting, the action was relative.
A <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:

Console
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:

contact.html
<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, formnovalidate and formtarget. A Save draft button with formnovalidate skips the browser's checks.
  • formaction only 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:

search.html
<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:

footer.html
<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

  1. Under the form's Allowed domains, add your site's domain, plus localhost while you develop. Leave it empty to accept posts from anywhere.
  2. Open the published page, not your source, and run document.querySelector("form").action in the console. It prints your full Ikiform URL.
  3. Submit the form. The Network panel shows a POST to ikiform.com answered with 303, not a POST to your own domain.
  4. In the two-inbox form, submit once with each button. Each submission appears under its own form in the dashboard.
  5. 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 email field.

Gotchas

ProblemCause and fix
405 Method Not Allowed from your own domainThe action is relative, empty or missing, so the post went to your static host. Use the full Ikiform URL
404 not_found from ikiform.comThe 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 actionSome 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 secureThe action uses http: on an https: page. Ikiform's endpoint is https: only; fix the scheme
Fields from a second form are posted tooForms 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

Stuck on a step, or want a guide for your situation? Email hello@ikiform.com.