Documentation

Your first submission, in minutes.

Create an endpoint. Add your form. Keep the frontend you love.

1. Create an endpoint

Open the dashboard, enter your email and sign in through the single-use emailed link, which expires in 20 minutes. This verifies your email for submission notifications. Your private access link is an optional backup. Create a form, then replace YOUR_FORM_ID below with its ID.

<form action="https://formdrop.wouldairecommend.workers.dev/f/YOUR_FORM_ID" method="POST">
  <label for="email">Email</label>
  <input id="email" type="email" name="email" required>
  <label for="message">Message</label>
  <textarea id="message" name="message" required></textarea>
  <input name="_gotcha" tabindex="-1" autocomplete="off" hidden>
  <button type="submit">Send message</button>
</form>

2. Read your first submission

Submit from your website and open Inbox in the dashboard. Use Spam to review filtered messages. Marking a message “Not spam” sends it to your connected integrations again. New submissions briefly appear as processing while classification finishes.

3. Connect your tools

Add Slack, Discord, Telegram or an HTTPS webhook under Integrations, then use Send test. Free includes one integration across all forms. FormDrop emails new non-spam submissions to the owner’s verified email. Verify it by signing in once through an emailed link.

Endpoint reference

POST /f/<formId> accepts URL-encoded, multipart and JSON bodies. Use Accept: application/json for {"ok":true}; JSON bodies also select JSON responses. Native forms receive a 303 redirect. OPTIONS supports CORS preflight.

Reserved fields

  • _gotcha: leave empty; filled values are silently accepted as spam.
  • _redirect: an HTTPS success URL, restricted to your configured domains.
  • _subject: notification subject, at most 160 characters.
  • _replyto: reply-to address; falls back to email. The ordinary email field remains visible in your inbox.
  • cf-turnstile-response: visitor token when a Turnstile secret is configured on Pro.

Control fields are stripped from saved data. File uploads: not yet. Files are ignored and the record shows that a file was dropped.

AJAX

Add id="contact" to your form and <p id="status" role="status"></p> after it. Load this script with defer.

const form = document.querySelector('#contact');
form.addEventListener('submit', async (event) => {
  event.preventDefault();
  const status = document.querySelector('#status');
  const button = form.querySelector('button');
  button.disabled = true;
  try {
    const response = await fetch('https://formdrop.wouldairecommend.workers.dev/f/YOUR_FORM_ID', {
      method: 'POST', body: new FormData(form),
      headers: { Accept: 'application/json' }
    });
    const result = await response.json();
    if (!response.ok) throw new Error(result.message || 'Could not send.');
    status.textContent = 'Message received. Thank you!';
    form.reset();
  } catch (error) { status.textContent = error.message; }
  finally { button.disabled = false; }
});

Domains, CORS and redirects

Enter hostnames such as example.com,www.example.org, without schemes or paths. A domain rule includes its subdomains. When restricted, requests must have a matching Origin, or a matching Referer if Origin is absent. An unrestricted form permits any source and any HTTPS redirect; choose domain restrictions when that is inappropriate. CORS reflects an allowed Origin and uses * only for unrestricted forms without an Origin. Domain rules are an abuse deterrent, not authentication for public forms.

Turnstile

On Pro, add the secret in Settings. Render Cloudflare Turnstile on your site with your public site key; its generated cf-turnstile-response token must accompany the submission. Failed verification rejects the request. Keep the secret in the dashboard, never in your public HTML.

Webhook payload

{
  "title": "New form submission",
  "text": "Contact",
  "level": "info",
  "url": "https://formdrop.wouldairecommend.workers.dev/app?form=f_example",
  "fields": {
    "email": "visitor@example.com",
    "message": "Hello"
  },
  "data": {
    "id": "s_example",
    "form_id": "f_example",
    "created_at": 1792368000000,
    "fields": {
      "email": "visitor@example.com",
      "message": "Hello"
    },
    "files_dropped": false
  },
  "product": "formdrop",
  "sent_at": "2026-10-19T00:00:00.000Z"
}

Integrations receive a single delivery attempt after spam checks. Delivery results appear with the submission. There is no automatic retry queue or signature on outbound webhooks yet. Use a unique, unguessable target URL; never trust a public endpoint alone as proof of origin.

Limits and errors

  • 64 KB total body; 100 fields; 10 KB combined text per field. Repeated names are joined with a newline.
  • 10 submissions per minute and 100 per day per form and hashed IP; 60 per minute across a form.
  • Monthly plan allowance plus 10% grace; spam also counts. Calendar months use UTC.
  • 400 invalid fields / Turnstile; 403 domain denied; 404 unknown form; 413 body too large; 415 unsupported content type; 429 rate or monthly limit.

JSON errors contain ok:false, error and message. Native submissions receive an explanatory HTML page. Store no passwords, payment card data or special-category personal data. Submission history is purged lazily when you list or submit; spam expires after 30 days.

Find your framework