The whole thing, on one page.

Flora is an address you post a form to. There is no library to install and no key to keep secret, so everything there is to know fits here.


Everything, in order

9 of 9

The endpoint

Create a form in your dashboard and you get an address. Put it in the action of any HTML form and set the method to POST. That is the integration.

html
<form method="POST" action="https://post.useflora.eu/pk_9f3c2a7d41b6e0c5">
  <input name="email" type="email">
  <textarea name="message"></textarea>
  <button>Send</button>
</form>

Every field needs a name, which is what it is called in your dashboard. Fields without one are not submitted by the browser and never reach us.

Submitting with fetch

The same address accepts JSON. Flora looks at your Accept header: a browser navigating a plain form asks for HTML and gets a thank-you page, and everything else gets JSON.

js
const response = await fetch('https://post.useflora.eu/pk_9f3c2a7d41b6e0c5', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email, message }),
})

const result = await response.json()
// { ok: true }

Form-encoded bodies work too, so an existing FormData post needs no changing. Cross-origin requests are allowed from anywhere the form accepts, and the preflight is handled.

Leave the redirect field empty in settings if you submit with fetch. A form with a redirect set answers every submission with a 302, including this one, and fetch follows it instead of handing you the JSON.

In your framework

There is nothing to install, so this is the same form four times with each language's punctuation. Astro, Eleventy, Hugo, Rails and plain HTML use the first example on this page unchanged.

Contact.jsx

html
export function Contact() {
  return (
    <form method="POST" action="https://post.useflora.eu/pk_9f3c2a7d41b6e0c5">
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button>Send</button>
    </form>
  )
}

No onSubmit, no state, no preventDefault. The browser posts the form and the page navigates to the thank-you response.

What happens to your fields

Everything is stored as text, under the name you gave it. Several inputs sharing a name, a group of checkboxes usually, are joined with a comma rather than dropped.

html
<input type="checkbox" name="days" value="Monday">
<input type="checkbox" name="days" value="Friday">

<!-- arrives as   days: "Monday, Friday" -->

Anything that is not a single value is discarded quietly: nested objects, arrays of objects, empty values with no field name. Fields beginning with an underscore are ours and are never stored, which at the moment means _gotcha and _redirect.

LimitValue
Fields in one submission60
Characters in one value20,000
Characters in one field name100
Size of the whole request256 kB
Submissions a minute, per form30

Go past one of the first four and the submission is refused with a 422 that says which one, rather than being trimmed to fit. The 256 kB cap is checked before any of that, so an oversized body gets a 413.

After a submission

Three possibilities, in this order:

  1. 1

    The redirect set in the form's settings, if there is one. Only you can set it, so it is used exactly as written.

  2. 2

    A _redirect field in the submission, if it points at the same site the form was submitted from.

  3. 3

    Otherwise a plain thank-you page, or { ok: true } if you asked for JSON.

Per-form redirect

html
<input type="hidden" name="_redirect" value="https://example.gr/thanks">

The same-site rule on _redirect is deliberate. Anyone can copy your address out of your HTML, and without it they could point your endpoint at anywhere they liked and send people there.

Spam

Add a field a person cannot see. A bot fills in everything it finds, so anything arriving with _gotcha filled in is thrown away. The response is an ordinary thank-you, because telling a bot it was caught only teaches it what to avoid.

Honeypot field

html
<div style="position:absolute;left:-9999px" aria-hidden="true">
  <label>
    Leave this empty
    <input type="text" name="_gotcha" tabindex="-1" autocomplete="off">
  </label>
</div>

Two more things happen without any markup. A form accepts thirty submissions a minute and answers 429 above that. And an identical submission to the same form inside twenty seconds is treated as the double click it almost always is: the sender sees success, and it is stored once.

There is no CAPTCHA and there will not be one. They are a tax on the people filling in your form, most of whom are not robots.

Locking a form to your sites

A new form accepts submissions from anywhere, so it works the moment you paste it in. Add a domain under allowed sites and everywhere else starts getting a 403.

One domain a line. An entry covers its own subdomains, so example.gr also allows www.example.gr.

Once a list exists, a submission that will not say where it came from is refused. A page opened from file:// is the usual way to meet this. Test over http://localhost and add it to the list while you are building.

When something goes wrong

A browser gets a plain page it can read. Anything else gets { ok: false, error: "…" } with the same wording, so you can show it as it is.

CodeWhat it means
400The body could not be read in the format its Content-Type claims.
403The site that posted is not on the form’s allowed list.
404The address is malformed, or no form has that key.
410The form exists but is switched off in its settings.
413The whole request was larger than 256 kB.
422The fields were rejected. The message names the field and the reason.
429More than thirty submissions in a minute. Wait and send again.
503We could not save it. Nothing was stored, so retrying is safe.

What Flora does not do

Better to read it here than to find out in production.

  • File uploads. A form with a file input will submit its other fields and drop the file.
  • Nested JSON. Objects and arrays of objects are dropped; send flat name and value pairs.
  • Webhooks, and posting a submission onward to another service.
  • Editing a submission. They are stored as they arrived, and can only be deleted.

Something here wrong, or missing? Write to [email protected] and a person will answer.