splitforms.com
All articles/ TUTORIALS9 MIN READPublished May 10, 2026

How to Add a Working Contact Form to Framer in 2026

Add a real contact form to your Framer site in 2026 — code embed, form-to-email backend, custom styling, and how to bypass Framer's built-in plan limits.

✶ Written by
splitforms.com / blog

Founder of splitforms — the form backend API for developers. Writes about form UX, anti-spam, and shipping web apps without backend code.

Why Framer's native form isn't enough

Framer added a real Form component a couple years back and it works — for the smallest sites. The problem is what it doesn't do, and what it forces you to pay for to do anything useful with the data you collect.

Here's the actual list of limits people hit, ranked by how often they cause migrations to a real form backend:

  • Submission caps tied to your Framer plan. Free and Mini plans are tightly capped. Even Basic ($5/mo as of 2026-05) only gives you 1,000 submissions across the entire site. If you run multiple landing pages, you blow through that fast.
  • Framer-branded notification emails. The email your visitor (or you) receives says "sent via Framer" in the footer. You cannot turn this off on any plan. For a serious brand, that's not acceptable.
  • No webhooks. You can't pipe submissions to Slack, Discord, Zapier, n8n, or a custom endpoint. Framer offers Mailchimp / ConvertKit integrations and that's the entire integration surface.
  • No spam filtering beyond a basic honeypot. Bot networks already know how to bypass Framer's honeypot. There is no AI classification, no reCAPTCHA-like fallback, no IP rate-limiting you can configure.
  • Zero data portability. Submissions live in your Framer dashboard. CSV export exists but the data structure is opaque, and if you ever move off Framer, you lose your history.
  • One sender address. You can't change the From address per form or per campaign. It's whatever Framer routes through.

The fix isn't to upgrade your Framer plan. The fix is to embed your own form HTML and post it to a backend that does this stuff properly. That's what the rest of this article walks through.

Step 1: Get a splitforms access key (1 minute)

Before you touch Framer, get the authentication key that'll wire your form to a real backend.

  1. Open splitforms.com/login in a new tab.
  2. Enter your email and paste the 6-digit code that arrives in your inbox.
  3. The dashboard auto-generates an access key — copy it. It looks like a long random string.
  4. Optional but recommended: under Security, add your Framer staging domain (e.g. yourproject.framer.website) and your production custom domain to the Allowed Domains list. This stops other sites from spamming through your key.

That key is the only piece of authentication you need. Free tier gives you 1,000 submissions/month forever, no credit card. If you outgrow it, Pro is $5/month for 5,000 submissions, or $59 for 4 years (average $1.23/month). Compare that to upgrading Framer plans purely to get more form quota.

Step 2: Build the HTML form

Before you open Framer, sketch the form in plain HTML. This is the exact markup you'll paste into Framer's embed component:

<form action="https://splitforms.com/api/submit" method="POST">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />

  <label>
    Name
    <input type="text" name="name" required />
  </label>

  <label>
    Email
    <input type="email" name="email" required />
  </label>

  <label>
    Message
    <textarea name="message" rows="5" required></textarea>
  </label>

  <!-- Honeypot — humans don't see this, bots fill it -->
  <input type="checkbox" name="botcheck" style="display:none" tabindex="-1" />

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

Replace YOUR_ACCESS_KEY with the key from step 1. Notice the form posts directly to splitforms.com/api/submit — no JavaScript fetch, no client library, no Framer plan dependency. The browser handles the POST and splitforms emails you the submission.

For framework-built sites consider the framework-native integrations: Next.js, React, Svelte. But for Framer, plain HTML is the right path — it keeps the embed lightweight and avoids React hydration weirdness inside Framer's iframe.

Step 3: Insert a Code Component or Embed in Framer

Framer offers two ways to drop raw HTML onto a page. They behave slightly differently and the right choice depends on how much custom logic you want.

Option A: Embed component (simplest)

  1. In the Framer editor, open the page where you want the form.
  2. Click the + insert button (or press I) and search for Embed.
  3. Drag the Embed component onto the canvas.
  4. In the right-side properties panel, paste your form HTML into the HTML field.
  5. Resize the embed's frame to fit. Default is 400×300px — most contact forms want at least 480×520.

The Embed component renders your HTML inside an iframe. That means your form is isolated from Framer's global styles, which is usually what you want — predictable styling, no surprise overrides.

Option B: Code Component (more control)

If you want the form rendered inline (no iframe), use a Code Component. In the editor, click +, choose Code Component, and create a new one. Paste a minimal React component:

import * as React from "react"

export default function ContactForm() {
  return (
    <form action="https://splitforms.com/api/submit" method="POST" style={{ display: "grid", gap: 12 }}>
      <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
      <input type="text"  name="name"    placeholder="Name"    required />
      <input type="email" name="email"   placeholder="Email"   required />
      <textarea           name="message" placeholder="Message" required rows={5} />
      <input type="checkbox" name="botcheck" style={{ display: "none" }} tabIndex={-1} />
      <button type="submit">Send</button>
    </form>
  )
}

Save and drag the component onto your page. It now renders as native DOM — no iframe — so it inherits the page's scrolling, fonts, and accessibility tree. The trade-off is Framer's global CSS can leak in and override your form styles in subtle ways. Pick Embed for safety, Code Component for full integration.

Styling: Framer tokens vs raw CSS

The default browser styling for inputs is ugly. You have two paths to make the form match your Framer brand.

Using Framer design tokens

Framer exposes your site's colour and typography choices as CSS variables. Inside your embed's HTML, add a <style> block referencing them:

<style>
  form { font-family: var(--token-font-body, system-ui); display: grid; gap: 12px; }
  input, textarea {
    padding: 12px 14px;
    border: 1px solid var(--token-color-border, #d4d4d4);
    border-radius: 8px;
    background: var(--token-color-surface, #fff);
    color: var(--token-color-text, #111);
    font: inherit;
  }
  button {
    padding: 12px 18px;
    background: var(--token-color-primary, #111);
    color: var(--token-color-on-primary, #fff);
    border: 0; border-radius: 8px; cursor: pointer;
    font-weight: 600;
  }
  button:hover { opacity: 0.9; }
</style>

If your Framer site is set up with named design tokens, those var(--token-...) references will resolve and your form picks up your brand colours automatically. If a token isn't defined, the fallback after the comma kicks in.

Raw CSS (pixel-perfect)

If tokens don't expose the variable you need, or you want exact control, hardcode the values:

<style>
  .sf-form input, .sf-form textarea {
    padding: 14px 16px;
    border: 1px solid #e5e5e5;
    border-radius: 10px;
    font-size: 16px;
  }
  .sf-form input:focus, .sf-form textarea:focus {
    outline: 2px solid #0066ff;
    outline-offset: 2px;
  }
</style>
<form class="sf-form" action="...">...</form>

Scope your CSS to a class on the form root (.sf-form) so it never bleeds into the rest of the page. This matters more for Code Components than Embeds — Embeds are already iframe-scoped.

One critical detail: set font-size: 16px on inputs. Mobile Safari zooms the viewport when an input has smaller text, which looks terrible. 16px or larger avoids the zoom.

Validation that actually works

HTML5 gives you free client-side validation without writing any JavaScript. Use it.

  • required — blocks submission if empty.
  • type="email" — browser enforces email-like format.
  • minlength="10" on the message — stops "hi" spam submissions.
  • maxlength="1000" — caps abuse where a bot pastes a novel.
  • pattern="[A-Za-z\\s]+" on name — prevents URL injections.
  • autocomplete="name", autocomplete="email" — better mobile UX, password managers pre-fill correctly.

The browser shows native error tooltips when validation fails. They're ugly but they work everywhere, including in Framer's iframe. If you want prettier validation, add a tiny inline script that listens for the invalid event and renders your own error UI. For most contact forms, native validation is fine.

Server-side validation happens automatically on splitforms' end — empty fields, invalid emails, and oversized payloads all get rejected before they reach your inbox. You don't need to duplicate it client-side unless you want a nicer UX.

Spam protection without reCAPTCHA

Framer's native form uses a basic honeypot. You can do better.

A honeypot is a hidden form field that real humans never see. Bots fill every input they find, so if the honeypot field has a value when the form submits, you know it's a bot. splitforms checks the botcheck field automatically — if it's non-empty, the submission is silently dropped.

<!-- The honeypot. Bots fill it; humans don't see it. -->
<input
  type="checkbox"
  name="botcheck"
  style="display:none"
  tabindex="-1"
  autocomplete="off"
/>

Three small details that matter:

  • tabindex="-1" — keyboard navigation skips the field so screen reader users don't accidentally hit it.
  • autocomplete="off" — password managers won't auto-fill it.
  • style="display:none" beats CSS class hiding because Framer's editor might strip unknown classes.

The honeypot stops about 80% of bot traffic. For the remaining 20% (sophisticated bots, click farms, headless browsers), splitforms adds free AI spam classification on top. Read the deep dive on honeypot vs reCAPTCHA for the trade-offs — short version: don't add reCAPTCHA to a Framer site, it kills mobile conversion rates.

Preview in Framer, then publish

Framer's Preview button (top right) opens a live preview in a new tab. Test the form there before publishing.

  1. Click Preview. Framer opens a temporary URL like preview.framer.app/....
  2. Fill in the form with a real email you can check.
  3. Hit Send. You should see a default success page from splitforms (or your custom redirect — see below).
  4. Check the inbox tied to your splitforms account. The email arrives within 5 seconds.
  5. Open splitforms.com → Submissions. Your test submission should be there.

If the email arrives and the dashboard shows the submission, you're done. Click Publish in Framer (top right) to push to your live domain.

Custom success redirect

By default, the user sees a splitforms thank-you page after submitting. Most people want their own. Add a hidden redirect field:

<input type="hidden" name="redirect" value="https://yourdomain.com/thanks" />

Build a /thanks page in Framer with whatever messaging you want — "Thanks, we'll reply within 24 hours" is the standard pattern. Don't skip this step. The default splitforms success page is plain by design, and you want visitors to stay on your domain.

Troubleshooting: canvas vs publish, mixed content, mobile UX

Things that go wrong with Framer embeds, in order of frequency:

  • Form looks broken in the canvas but works on Preview. The canvas renders your embed inside a different iframe than Preview. CSS positioning and form submissions both behave differently. Always test on Preview or the published URL — never trust the canvas for form behavior.
  • Submission silently fails with no error. Open browser DevTools (F12) on the published page and check the Network tab. If you see a CORS or 401 error, your access key is wrong or your Allowed Domains list excludes the current origin. Add yourproject.framer.website and your custom domain.
  • Mixed-content warning in console. Your form action URL must be HTTPS. https://splitforms.com/api/submit is correct; http:// will be blocked by the browser on any HTTPS-served Framer page.
  • Mobile keyboard pushes the submit button off-screen. This is a Framer layout issue, not a form issue. Set your form container to min-height: 100dvh instead of 100vh so it accounts for the mobile keyboard.
  • iOS Safari zooms when the user taps an input. Inputs with font-size below 16px trigger the zoom. Set font-size: 16px minimum on all inputs and textareas.
  • Email never arrives. Check your spam folder first. If it's clean, the splitforms dashboard will still show the submission — confirming it reached the backend. Deliverability issues are usually a misconfigured custom SMTP, not splitforms itself. See contact form not working for the full diagnostic flow.
  • Form double-submits on mobile. Users tap submit twice. Add a tiny inline script that disables the button on submit: onsubmit="this.querySelector('button').disabled=true".
  • The honeypot is catching real users. Disable autocomplete on the field and double-check that display: none is inline, not in a stylesheet Framer might strip.

For deeper issues, the splitforms FAQ and the API reference cover the request contract, headers, and error codes. If a real submission silently fails, email support with the access key prefix and the timestamp of the attempt.

Next steps

FAQ

Does Framer have a built-in contact form?

Yes, Framer ships a native Form component, but it has real limitations on the Free and Mini plans — submission caps, Framer-branded notification emails, no webhooks, and no AI spam filtering. On Basic and above it gets better, but you still can't customise the email template or pipe submissions to a webhook. For most indie sites, a code-embedded form pointed at splitforms is cheaper and more flexible.

Will my custom form work inside the Framer canvas preview?

The canvas preview runs inside an iframe that often blocks third-party POSTs and shows mixed-content warnings. The form will render fine but submissions might silently fail. Always test on the published URL (framer.website or your custom domain) — that's the only reliable environment. If the embed looks broken on canvas but works on the live URL, the canvas iframe is the cause, not your form.

Can I use file uploads with this setup?

Yes. Change the form's `enctype` to `multipart/form-data` and add an `<input type="file" name="attachment" />`. splitforms accepts files up to 10 MB on the free tier and 25 MB on Pro. Framer's Embed component doesn't strip file inputs, so the upload just works once you publish.

How do I style the form to match my Framer site?

Two options. Use Framer's design tokens (var(--token-color-primary), var(--token-font-body)) inside a `<style>` block in your embed so the form inherits your site's palette and typography. Or paste raw CSS scoped to a class on the form root. Tokens are cleaner because if you change your Framer brand colour, the form updates automatically. Raw CSS gives you pixel-perfect control if tokens don't expose the variable you need.

What about CORS errors when submitting?

splitforms accepts cross-origin POSTs from any domain by default. If you've enabled Allowed Domains in your dashboard security settings, add both your Framer staging domain (`yourproject.framer.website`) and your production custom domain. Forgetting the staging domain is the #1 cause of "works on prod, 401 in preview" reports.

Why is my form getting spam even with the honeypot?

The honeypot stops dumb bots — about 80% of form spam. The remaining 20% is human-driven or headless-browser spam that fills every field. splitforms layers AI classification on top of the honeypot for free, so genuinely sophisticated spam still gets caught. If you're seeing more than a handful per week, check the dashboard's spam tab — most of it is probably already filtered.

Does this work with Framer's CMS collections?

Yes, but it's a different pattern. The contact form pushes data out to splitforms (and then to email). A CMS collection pulls data in for rendering. If you want a form that writes into a Framer CMS collection, that's a different product entirely — Framer's native Form connected to its CMS. For lead capture, contact forms, signups, and notifications, splitforms is the right tool.

What does splitforms cost compared to upgrading my Framer plan?

Framer's Basic plan is $5/month and bumps you to 1,000 form submissions. splitforms gives you 1,000 submissions/month free, $5/month Pro for 5,000, or $59 for 4 years (averages ~$1.23/month). The bigger win is features: webhooks, AI spam, MCP integration, and your own email template — none of which Framer offers at any plan tier.

About the author
✻ ✻ ✻

Get your free contact form API key in 60 seconds.

1,000 free form submissions per month. No credit card. No SDK, no PHP, no plugin. Drop one POST endpoint in your form and submissions land in your inbox.

Generate access key →Read the docs
v0.1 · founders pricing locked in · early access open