Framer contact form to email from your own domain
In Framer, build the form as a code component. A code component renders on the page itself, so FormRoute can see which website the submission comes from.
You need a FormRoute account with a verified domain and a form whose fields are name, email and message, or change the names below to match yours. Getting started covers the setup.
1. Why not the Embed element
Framer's Embed element renders HTML inside an iframe. A request from inside it may not carry your website's address, and FormRoute refuses submissions it cannot match to one of your allowed websites.
2. Create a code component
In the Assets panel, create a new code component, replace its contents with the code below and replace fr_your_site_key with the site key from your FormRoute account.
import { useEffect, useRef, useState, type FormEvent } from "react";
const ENDPOINT = "https://form-route.com/api/v1/send";
const SITE_KEY = "fr_your_site_key"; // from your FormRoute account
export default function FormRouteContactForm() {
const shownAt = useRef(0);
const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
// Start the clock when the form is on screen.
useEffect(() => {
shownAt.current = Date.now();
}, []);
async function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const form = event.currentTarget;
const data = new FormData(form);
setStatus("sending");
try {
const response = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
siteKey: SITE_KEY,
t: Date.now() - shownAt.current,
hp: data.get("hp") ?? "",
name: data.get("name"),
email: data.get("email"),
message: data.get("message"),
}),
});
if (response.ok) {
form.reset();
setStatus("sent");
} else {
setStatus("error");
}
} catch {
setStatus("error");
}
}
return (
<form onSubmit={onSubmit}>
<label>Name <input name="name" required maxLength={120} /></label>
<label>Email <input name="email" type="email" required maxLength={254} /></label>
<label>Message <textarea name="message" required maxLength={5000} /></label>
{/* Honeypot: hidden from people, must stay empty */}
<div aria-hidden="true" style={{ position: "absolute", left: -10000 }}>
<label>Leave this empty <input name="hp" tabIndex={-1} autoComplete="off" /></label>
</div>
<button type="submit" disabled={status === "sending"}>Send</button>
<p role="status">
{status === "sent" && "Thanks, your message was sent."}
{status === "error" && "Sorry, that didn't go through. Please try again."}
</p>
</form>
);
}3. Place it and publish
Drag the component onto the page, style it, and publish the site.
4. List your website in FormRoute
In your FormRoute account, open your form and add every address the form is served from to Allowed websites, for example https://www.example.com and, while you test, your Framer-provided address. Each entry is a scheme and host, with a port only if your site uses one.
A browser submission from an address that isn't listed is refused, and your account shows it as Rejected: website not allowed. For local development, use the exact address your dev server prints.
5. Send a test
Open the page, fill in the form and send it. The submission appears on your FormRoute home page within seconds, as Sent or with the reason it was refused, and the email arrives from your own domain.
Fill the form in at a human pace: a submission sent within 1.2 seconds of the form appearing is treated as automated.
Good to know
- Framer's built-in form element sends its own payload and cannot measure how long the form was open, which FormRoute's spam checks rely on. Use the code component.
More guides
HTML · Next.js · React · Vue · Astro · WordPress · Webflow · Hugo, Jekyll and Eleventy