Docs // Getting started

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.

Quickstart

Three minutes, start to finish

There is no SDK, no build step, and nothing to install. The whole integration is a URL in your form's action attribute.
  1. 01

    Create an endpoint

    Sign up and name a form. You get the URL below and a searchable inbox immediately.

  2. 02

    Point your form at it

    Set your form’s action, or POST with fetch. No JavaScript required, no SDK to install.

  3. 03

    Confirm your email once

    Click the link we send. From then on, filtered submissions arrive with the sender in Reply-To.

Your endpointa public address, not a secret
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.
Integrations

Add it to your stack

Every snippet is complete: it handles success and failure, includes the spam honeypot, and contains no secret. Copy the one for your framework.

Replace your existing <form> tag, or just change its action attribute.

html
<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 guide
Form options

What your form can do

Grouped by the job each one does. Every option says where the switch lives and what happens if you never touch it, because a setting you have to reverse-engineer is not a feature.

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
_mtf_honeypotField or settings

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
_mtf_turnstile_tokenField or settings

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.

Where 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
_mtf_subjectField or settings

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
_mtf_reply_toField or settings

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
_mtf_form_nameForm field

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.

What 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
_mtf_redirectField or settings

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.

Your 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.

Control fields

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.

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