Get your free splitforms access key
Sign up at splitforms.com, verify your email, and copy your access key from the dashboard. No credit card required.
Contact form · Framer
Framer's built-in forms send to a single email, gate webhooks behind paid plans, and limit free submissions. Paste a Code Component instead — same canvas drag-and-drop, real backend, 500 free submissions total, dashboard search, and signed webhooks on paid plans.

No server, API route, or SDK. Your Framer form posts straight to one endpoint.
Every submission is emailed to you and saved to a searchable dashboard — spam filtered before it reaches you.
It's your own Framer markup and styles. Splitforms is only the backend, so nothing constrains how the form looks.
Copy-paste ready
Replace YOUR_ACCESS_KEY with the key from your dashboard — that's the whole integration. No SDK to install, no build step, just the tsx you already write.
// ContactForm.tsx — paste into Framer's Code Components panel
// Insert the resulting component into any frame on the canvas.
import { useState } from "react";
import { addPropertyControls, ControlType } from "framer";
export default function ContactForm({ accessKey = "YOUR_ACCESS_KEY" }) {
const [status, setStatus] = useState<"idle" | "loading" | "ok" | "err">("idle");
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus("loading");
const form = e.currentTarget; // capture before the await — React clears currentTarget afterwards
const formData = new FormData(form);
formData.append("access_key", accessKey);
const res = await fetch("https://splitforms.com/api/submit", {
method: "POST",
body: formData,
});
const data = await res.json();
setStatus(data.success ? "ok" : "err");
if (data.success) form.reset();
}
return (
<form onSubmit={handleSubmit} style={{ display: "grid", gap: 12 }}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
<input type="checkbox" name="botcheck" style={{ display: "none" }} tabIndex={-1} />
<button type="submit" disabled={status === "loading"}>
{status === "loading" ? "Sending…" : "Send"}
</button>
{status === "ok" && <p>Thanks!</p>}
{status === "err" && <p>Something went wrong.</p>}
</form>
);
}
addPropertyControls(ContactForm, {
accessKey: { type: ControlType.String, title: "Access Key" },
});How to add it
To add a contact form to a Framer website you need three things: a free splitforms access key, the tsx snippet above, and your key pasted into it. No backend, server, or SDK — the form posts to one URL and every submission lands in your inbox and dashboard.
Sign up at splitforms.com, verify your email, and copy your access key from the dashboard. No credit card required.
Copy the Framer code example into your project and replace YOUR_ACCESS_KEY with the key from step 1.
Submissions arrive in the splitforms dashboard within seconds. Free includes owner email notification attempts; Pro adds Slack, Discord, Sheets, or any signed webhook URL.
Where submissions go
Every submission is emailed to you and saved to a searchable dashboard — spam filtered before it ever reaches you. Search, export to CSV, or forward it to a webhook or Slack on Pro.

No backend needed
Your Framer form posts standard FormData to one URL. Splitforms validates the access key, runs its spam checks, and forwards it to your email — so there's no server, API route, or database for you to build or maintain.

Best practices
The difference between a form that works in the demo and one that survives launch traffic — the production-tested defaults, in priority order.

How SplitForms works
splitforms receives the submission, filters spam, saves it to your dashboard and emails you — then, on paid plans, sends it on to your webhooks and integrations. No backend infrastructure to build.

Point your form to your unique SplitForms endpoint. That's it.

We instantly capture and organize every submission in your inbox.

Send data to email, spreadsheets, CRMs, webhooks, and 7,000+ apps.

No credit card required. Set up in under 60 seconds.
Connect & automate
On paid plans, submissions can also go to Slack, Discord, Google Sheets, Notion, Airtable, Zapier or any signed webhook — and splitforms works on the platforms you build on, from WordPress and Shopify to Next.js. With Stripe connected, your forms can take payments too.

Questions
Open the Code Components panel in Framer (sidebar → Components → New Code Component). Paste the snippet above. Save. The component appears in the Insert menu — drag it onto any frame. Set the accessKey property in the right panel.
Yes. Framer's published sites are static React + JS, and Code Components run in the browser. The form posts directly to splitforms.com — no Framer-side configuration beyond the Code Component itself.
Use a status state with four values (idle/loading/ok/err) inside the Code Component. Render error messages from the splitforms response. Style them with inline styles since Framer's CSS modules don't apply inside Code Components.
Not directly — Framer's built-in form widget posts to Framer's backend. Replace it with the Code Component to use splitforms. The Code Component renders inside Framer just like a native widget.
Two options. (1) Render an inline success message or set window.location after a successful fetch. (2) For a native form post, add a hidden redirect field to the Code Component pointing at your thank-you URL — free on every plan — or configure a default in Dashboard → Form settings → Redirect.
Yes — both. Framer's preview executes Code Components (so you can test the form in the editor), and the published site embeds them as React components. Splitforms-side, lock the access key to both your *.framer.app domain and your custom domain.
Yes. In the Framer editor use Insert → Embed and choose HTML, then paste a plain splitforms <form> (or a self-contained iframe with the form in its srcdoc). This drops an Embed component onto the canvas wherever the form should live — a contact section, a footer, or a dedicated Contact page. The form posts straight to splitforms with no external scripts, and the hidden honeypot field still stops bots. The Code Component route is better when you want property controls and inline success states; the Embed route needs zero React knowledge.
Framer reads property controls only if addPropertyControls(Component, {...}) is called after the component is exported. If you put it before the export or wrap it in a conditional, the access-key control disappears from the right panel and you can't set the key visually.
Editing the access key in the right panel re-mounts the form mid-edit. If a user is testing the form when you change the key, their status === 'loading' state resets to 'idle' visually but the in-flight fetch still completes. Not a bug in production — only an editor quirk.
Framer's published sites have a default Content Security Policy that allows known integrations. Splitforms isn't on the default allowlist — you may need to add connect-src https://splitforms.com via Framer's Site Settings → Custom Code → Head HTML, inside a <meta http-equiv="Content-Security-Policy" content="…"> tag.
If you use style={{ fontFamily: '...' }} in a Code Component, you have to import the font manually — Framer's site fonts only auto-apply to canvas-built elements. Use font-family: inherit to inherit from the parent frame.
Browser-default focus rings are sometimes hidden by Framer's reset CSS. Add :focus { outline: 2px solid currentColor; outline-offset: 2px; } in your component's styles — accessibility requires a visible focus indicator.
If your contact form Code Component lives inside a Smart Component variant (e.g. a 'Mobile' variant of a header), Framer re-instantiates the entire Code Component when switching variants — losing your useState for status. A user mid-submission who rotates their device sees the form reset to 'idle' while the fetch is still in flight, then a stale success/error notification when the request resolves into a now-unmounted component (React logs a 'state update on unmounted component' warning). Either lift the form out of any Smart Component variant, or persist status to sessionStorage and rehydrate on mount.
Framer's built-in form widget delivers submissions to a single email address (configured per form) with no dashboard for managing submissions, no webhooks below the Pro plan, and no spam filtering beyond Framer's basic bot detection. CMS-driven Framer forms inherit the same constraints. The native flow works for a portfolio's contact form; it falls apart for any setup that needs Slack/Discord notifications, multiple recipients, CSV export, or tagged form-name routing. Replacing it means writing a Code Component — Framer's mechanism for embedding custom React. That's the pattern splitforms uses: a Code Component with the access key as a property control, dropped onto the canvas like a native widget.
Framer publishes sites to its own CDN — there's no Vercel/Netlify config to manage. The form posts cross-origin to splitforms regardless. Framer's published sites have a default Content Security Policy that may block connect-src to splitforms.com on some plans; if submissions silently fail, add <meta http-equiv="Content-Security-Policy" content="connect-src https://splitforms.com"> via Site Settings → Custom Code → Head HTML. Lock the access key to BOTH your *.framer.app preview URL AND your custom domain — Framer serves both with different Origin headers. Framer's preview environment runs Code Components live, so you can test the form before publishing.
Simple pricing
Free gives you 500 submissions for $0 — no card, no time limit. Pro is $5/month for 5,000 submissions a month, and Annual is $49/year for 15,000 a month with every Pro feature.
For side projects and indie devs.
Every paid feature, for agencies and growing products.
Every Pro feature with triple the volume — $0.27 per 1,000 at full use.
No credit card required on Free • Cancel anytime • Full pricing and FAQ