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.
<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.
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.
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.
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.
<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.
| Limit | Value |
|---|---|
| Fields in one submission | 60 |
| Characters in one value | 20,000 |
| Characters in one field name | 100 |
| Size of the whole request | 256 kB |
| Submissions a minute, per form | 30 |
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
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
A
_redirectfield in the submission, if it points at the same site the form was submitted from. - 3
Otherwise a plain thank-you page, or
{ ok: true }if you asked for JSON.
<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.
<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.
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.
| Code | What it means |
|---|---|
| 400 | The body could not be read in the format its Content-Type claims. |
| 403 | The site that posted is not on the form’s allowed list. |
| 404 | The address is malformed, or no form has that key. |
| 410 | The form exists but is switched off in its settings. |
| 413 | The whole request was larger than 256 kB. |
| 422 | The fields were rejected. The message names the field and the reason. |
| 429 | More than thirty submissions in a minute. Wait and send again. |
| 503 | We 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.