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
- Select your form element, open the settings panel and choose Type: External.
- Paste your endpoint URL into the Action field and set Method to POST.
- Name each field exactly as you want it stored — e.g.
email,message. - Add a hidden field named
_redirectwith your thank-you page URL, or set the redirect once in dashboard settings. - Carrd Pro users can add a hidden
_gotchafield 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
_gotchafield 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.