Ship a form
in three minutes
One endpoint, any front end. Point a form at it and receive spam-filtered submissions by email and in a searchable inbox: no server, no iframes, no JavaScript required.
Three minutes, start to finish
- 01
Create an endpoint
Sign up and name a form. You get the URL below and a searchable inbox immediately.
- 02
Point your form at it
Set your form’s action, or POST with fetch. No JavaScript required, no SDK to install.
- 03
Confirm your email once
Click the link we send. From then on, filtered submissions arrive with the sender in Reply-To.
https://mtform.co/f/your-form-key- Host
- Short by design, because you will paste it a lot. api.maketheform.com serves the identical endpoint if you prefer the long form.
- Path
- One route, one method. POST is the only verb the endpoint answers.
- Your form key
- Issued when you create a form. Safe to commit, safe to ship in client-side HTML, and rotatable if you ever need to.
Add it to your stack
Replace your existing <form> tag, or just change its action attribute.
<form action="https://mtform.co/f/your-form-key" method="POST">
<label>
Name
<input name="name" autocomplete="name" required>
</label>
<label>
Email
<input type="email" name="email" autocomplete="email" required>
</label>
<label>
Message
<textarea name="message" required></textarea>
</label>
<!-- Spam trap: hidden from people, tempting to bots. Leave it empty. -->
<input
type="text"
name="_mtf_honeypot"
tabindex="-1"
autocomplete="off"
aria-hidden="true"
style="position:absolute;left:-9999px"
>
<button type="submit">Send</button>
</form>The browser navigates to a branded success page. Configure a redirect in Settings.
Full HTML guideWhat your form can do
Spam and abuse
Four layers run before a submission is stored, cheapest first. The first two are yours to configure. The last two need nothing from you.
- Honeypot
A hidden input a person never sees and a bot fills anyway. A non-empty value is absorbed: the bot gets a success response, you get no submission, and your quota is untouched.
Default On for every new form. Add the field to your markup and it starts working.
Hide it with position:absolute;left:-9999px, never display:none or type="hidden". Bots skip those, which defeats the point. Never mark it required.
- Bot challenge
Cloudflare Turnstile, verified on our side before the submission is accepted. For a form already under attack, where the honeypot alone has stopped holding.
Default Off. Enable it per form once a Turnstile site key is saved in Settings.
A token is single-use and short-lived. Rendering the widget without sending its token back fails every submission with challenge_failed.
- Allowed domains Form settings
Restricts the form to the origins you actually publish it on. Anything else is refused with origin_not_allowed.
Default Unset, so any origin may submit. The endpoint is public by design.
This list is also the redirect allowlist. A redirect to a domain missing from it falls back to the success page instead of following the link.
- Content scoring Always on
Every submission is scored. Likely spam goes to a spam view you can review and restore. Near-certain spam is refused and never counts against your quota.
Default Nothing to configure. Filtered submissions are held, never deleted.
- Rate limits Always on
Per IP and form: 5 in 30 seconds, then a 429 carrying a Retry-After header. Windows are short and recover on their own.
Default Checked before your quota, so a flood costs an attacker their patience and costs you nothing.
_mtf_honeypotField or settings_mtf_turnstile_tokenField or settingsWhere submissions go
Storage and delivery are separate systems. A submission is saved the moment it is accepted; everything here only shapes the notification that follows.
- Recipients Form settings
The confirmed addresses a notification is sent to. Free allows 1, Pro 5, Agency 10.
Default The address you signed up with, pending one confirmation click.
Until an address is confirmed, submissions are stored and no email is sent. An unconfirmed address is how a form backend becomes a spam relay, so this one is not negotiable.
- Subject line
Sets the subject of the notification. Send it per submission from the markup, or set one template for the whole form.
Default A subject naming the form.
- Reply-To
Puts the submitter in Reply-To, so answering a contact form is a keypress rather than a copy and paste.
Default The submitter address detected in the submission. You rarely need to set this yourself.
- Form name
A human label carried into the notification. Useful when one endpoint serves several pages and you want to know which one fired.
Default The name the form has in your dashboard.
- Field order Form settings
The order fields appear in the email and the inbox. Put the answer you read first at the top.
Default The order the fields were submitted in.
_mtf_subjectField or settings_mtf_reply_toField or settings_mtf_form_nameForm fieldWhat the visitor sees
The endpoint answers a navigating browser and a fetch call differently. The choice comes from request headers, never from sniffing the user agent.
- Redirect after submit
Sends the browser to your own thank-you page after a classic HTML post. It is a 303, so refreshing the page cannot resubmit the form.
Default A plain success page on our domain.
The target must be on the form’s allowed domains. Anything else shows the success page instead. An open redirect on an endpoint embedded across thousands of sites is a gift to phishers.
- JSON response Request header
Send Accept: application/json, or post JSON, and you get JSON back: a submission id on success, a code and a safe message on failure.
Default A browser sending Accept: text/html gets an HTML page. Everything else already gets JSON.
- Pause the form Form settings
Stops accepting submissions without deleting the form or invalidating the endpoint. Callers get form_disabled.
Default Active from the moment it is created.
_mtf_redirectField or settingsYour data
What is kept, for how long, and how you get it back out. None of it depends on email working.
- Retention Form settings
Submissions are kept 30 days on Free and 1 year on Pro. Agency is configurable up to 3 years.
Default Your plan’s window, applied per form.
- CSV export Always on
Export any filtered view of your inbox. Values are quoted to RFC 4180 and neutralized, so a formula somebody typed into your contact form cannot execute when the file opens in a spreadsheet.
Default Available on every plan, including Free.
- Idempotency Request header
Send an Idempotency-Key header and a retry returns the original result instead of creating a second submission. Keys are honored for 24 hours.
Default Without one, a retried request is a new submission. Worth adding to anything that retries on its own.
Reusing a key with a different payload is a 409, not a silent overwrite. Generate the key per form load, not per session.
- Rotate the key Form settings
Issues a new public form key and retires the old one. For when an endpoint is being hammered by something you cannot block by origin.
Default Keys do not expire. A retired key returns form_not_found, which reveals nothing about why.
Every place the old endpoint is deployed stops working the moment you rotate. Update your markup first.
- Payload limits Always on
Up to 100 fields and 256 KB per submission. An oversized request is refused rather than quietly truncated.
Default Applies to every form on every plan. The full table is in the API reference.
All of it, in one form
Options set from markup are namespaced _mtf_ so they cannot collide with your own field names. They travel with the submission and are removed before it is stored: they never appear in your inbox, in the email, or in a CSV export.
Anything you send that starts with that prefix and is not a name we recognise is ignored rather than rejected, so a newer field cannot break an older form.
<form action="https://mtform.co/f/your-form-key" method="POST">
<input name="email" type="email" required>
<textarea name="message" required></textarea>
<!-- Spam trap. Hidden from people, tempting to bots, always empty. -->
<input
name="_mtf_honeypot"
tabindex="-1"
autocomplete="off"
aria-hidden="true"
style="position:absolute;left:-9999px"
>
<!-- Subject of the notification email. -->
<input type="hidden" name="_mtf_subject" value="New quote request">
<!-- Labels the notification when one endpoint serves several pages. -->
<input type="hidden" name="_mtf_form_name" value="Pricing page">
<!-- Where the browser lands afterwards. Must be an allowed domain. -->
<input type="hidden" name="_mtf_redirect" value="https://example.com/thanks">
<button type="submit">Send</button>
</form>Ready for the details?
The wire format, every error code with the condition that produces it, rate limits, and idempotency.