Docs

Point any form at your Formrelay endpoint URL. No JavaScript required, no backend of your own. Replace YOUR_ENDPOINT_ID below with the URL shown on your dashboard.

Plain HTML

The whole product is one form action attribute.

<form action="https://your-app.lovable.app/api/public/f/YOUR_ENDPOINT_ID" method="POST">
  <input type="email" name="email" required />
  <textarea name="message" required></textarea>

  <!-- honeypot: keep it hidden, bots fill it in -->
  <input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off" />

  <!-- optional: where the browser lands after a successful post -->
  <input type="hidden" name="_redirect" value="https://yoursite.com/thanks" />

  <button type="submit">Send</button>
</form>

Browser posts get a thank-you page, or a 302 to your _redirect URL (or the redirect set in dashboard settings). Requests sending accept: application/json always get JSON back.

Carrd, Framer and Webflow

  1. Select your form element, open the settings panel and choose Type: External.
  2. Paste your endpoint URL into the Action field and set Method to POST.
  3. Name each field exactly as you want it stored — e.g. email, message.
  4. Add a hidden field named _redirect with your thank-you page URL, or set the redirect once in dashboard settings.
  5. Carrd Pro users can add a hidden _gotcha field as the honeypot.

File uploads

Add enctype="multipart/form-data" and a file input. Files are stored privately and appear as download links in your inbox. Files over the per-form limit (default 5 MB, max 10 MB) are rejected with a 413 and a clear message.

<form action="https://your-app.lovable.app/api/public/f/YOUR_ENDPOINT_ID" method="POST" enctype="multipart/form-data">
  <input type="email" name="email" required />
  <input type="file" name="attachment" />
  <button type="submit">Send</button>
</form>

JSON and curl

const res = await fetch("https://your-app.lovable.app/api/public/f/YOUR_ENDPOINT_ID", {
  method: "POST",
  headers: { "content-type": "application/json", accept: "application/json" },
  body: JSON.stringify({ email: "jane@example.com", message: "Hello!" }),
});

const result = await res.json();
// { ok: true, id, spam_score, spam_status, files, email, webhook, usage }
curl -X POST https://your-app.lovable.app/api/public/f/YOUR_ENDPOINT_ID \
  -H "accept: application/json" \
  -F "email=jane@example.com" \
  -F "message=Hello from curl" \
  -F "attachment=@/path/to/file.pdf"

Signed webhooks

Set a webhook URL in settings and every clean submission is POSTed to it, signed with your form’s signing secret and retried once on a non-2xx response. Delivery status shows in your inbox.

import crypto from "node:crypto";

// Formrelay sends:
//   x-formrelay-timestamp: 1730000000
//   x-formrelay-signature: sha256=<hex>
export function verify(rawBody, headers, secret) {
  const ts = headers["x-formrelay-timestamp"];
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  const a = Buffer.from(headers["x-formrelay-signature"] ?? "");
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Spam, rate limits and allowed domains

  • Honeypot: any value in the hidden _gotcha field scores 80.
  • Heuristics: spam keywords, link stuffing, shouting, very short messages, plus disposable inbox domains and abusive + aliases.
  • Rate limit: per visitor and per form, default 10 posts per minute. Excess posts get a 429 with retry-after: 60.
  • Allowed domains: optional list of hostnames. Posts whose Origin or Referer is elsewhere get a 403. Empty means accept everywhere.
  • Monthly cap: Free stores 100 submissions per month, counted on the server; over the cap the endpoint answers 429.

Cloudflare Turnstile

Turn the bot check on in settings, paste your site key, and drop the widget into your form. Verification runs server-side on every post.

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<form action="https://your-app.lovable.app/api/public/f/YOUR_ENDPOINT_ID" method="POST">
  <input type="email" name="email" required />
  <div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
  <button type="submit">Send</button>
</form>

Autoresponse

Enable the autoresponse in settings to email the submitter a confirmation whenever a clean submission comes in. Formrelay uses the email field (or the first email-looking value) as the recipient.

Response codes

  • 200 — stored (HTML thank-you, 302 redirect, or JSON).
  • 403 — domain not in the allowlist.
  • 404 — unknown endpoint ID.
  • 413 — file over the size limit.
  • 429 — rate limit or monthly cap reached.