Why Notion can't actually do contact forms
Notion is a beautiful editor with no email infrastructure behind it. The product is built to organize pages, databases, and embeds — not to receive HTTP POSTs, deliver mail, or filter spam. So when you publish a Notion page as a site, the "form" you see there is one of three things, none of which are a real backend:
- A database with a Form view. It writes rows into Notion, but there is no email notification, no webhook, no spam protection, and no domain restriction.
- A third-party widget pasted as an /embed. It works, but it's an iframe to someone else's service — Tally, Typeform, Google Forms — with their branding and their pricing.
- A mailto: link. Exposes your address to scrapers, depends on the visitor having a desktop mail client configured, and silently fails on mobile.
The fix is straightforward. Almost everyone shipping a Notion-built site in 2026 is doing it through one of three publishing layers — Super.so, Potion.so, or Feather — and all three let you paste raw HTML into the page or globally. That HTML can be a splitforms form, which gives you email delivery, AI spam filtering, webhooks, and a dashboard, on a free tier that covers most personal sites. The rest of this guide walks through the exact paste-in flow for each builder, plus styling, validation, and the troubleshooting steps for the embed quirks each one has.
Step 1: Get a splitforms access key (1 minute)
You need one access key, total, for your whole Notion site. Get it before you open your builder so you can paste it once and forget about it.
- Go to splitforms.com/login.
- Type your email, paste the 6-digit code, you're in.
- The dashboard shows your access key under the first form. Copy it — it's a 24+ character string.
- Under Settings → Allowed Domains, add the domain you'll publish to (e.g.
yourdomain.com) so nobody else can hit your endpoint with the key.
No credit card, no plan picker, no "7-day trial that turns into a charge." The free tier is 1,000 submissions a month and it stays free unless you upgrade. If you're comparing this against other backends first, read best free form backend services 2026 — splitforms tops it on free-tier headroom and webhooks.
Step 2: Build the HTML form (once, reusable)
This is the form you'll paste into whichever Notion builder you use. Keep it minimal — Notion sites read best with restraint. Two text inputs, a textarea, a submit button, a hidden honeypot.
<form action="https://splitforms.com/api/submit" method="POST" class="sf-form">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<input type="hidden" name="redirect" value="https://yourdomain.com/thanks" />
<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: real users never see this; bots fill it and get blocked -->
<input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off" />
<button type="submit">Send message</button>
</form>Two notes before you paste this anywhere:
- Replace
YOUR_ACCESS_KEYwith the string you copied in step 1. - Replace the redirect URL with a real page on your Notion site (a /thanks page works — see step 6). If you skip
redirect, splitforms shows its own neutral success page, which is fine but off-brand.
That's the entire form contract. Field names are arbitrary — splitforms takes whatever you send and emails it back to you. If you want phone or company fields, add them with name="phone" and name="company". The endpoint doesn't care about schema.
Step 3a: Embed in Super.so
Super.so is the most-used Notion site builder in 2026 and the friendliest for raw HTML. You have two places to paste: Global Code Injection (best for the CSS) and HTML block / Code embed (best for the form itself).
Paste the form into a page
- Open your Notion source page (the one Super mirrors). Add a new block: type
/embedand pick the embed primitive. - In Super's dashboard, go to your site → Customize → Code Injection → Head for the CSS, and Customize → HTML Embed for the form HTML.
- Or simpler: drop a Super HTML block directly on the Notion page using Super's
!htmlsyntax (their docs call it HTML blocks). Paste the form code from step 2 inside it. - Hit publish in Super. The form appears on the next deploy, usually within 10 seconds.
Super quirks to know
- Super wraps the page in its own container; if you paste
position: absoluteCSS, it will escape the column. Stick with normal flow. - Super's preview cache is aggressive. After publishing, hit your live URL in an incognito window — the editor preview can be 30–60 seconds behind.
- If you want the form on every page footer, paste it in Code Injection → Body End wrapped in a div with a class — then position it with CSS.
Step 3b: Embed in Potion.so
Potion has a similar architecture to Super but the panel names are different. Look for Custom Code under your site settings.
- Potion dashboard → your site → Settings → Custom Code.
- Paste the CSS block (from step 4 below) into Head.
- For the form HTML, Potion supports a Code Block in the Notion source page itself: add a code block, set the language to
html, paste the form, then in Potion's page settings enable Render HTML in code blocks. - Save and republish.
Potion quirks to know
- The "Render HTML in code blocks" toggle is per-page in some Potion plans and global in others. If your form shows up as literal HTML text, you forgot to flip the switch.
- Potion's default code-block styling has a gray background. Add
.notion-code { background: transparent; padding: 0; }to the head CSS so your form sits on a clean canvas. - Potion strips
<script>tags from code blocks for safety. The plain HTML POST flow in step 2 doesn't need any JavaScript, so this doesn't affect you. If you want AJAX submit, add the script via Custom Code → Head instead.
Step 3c: Embed in Feather
Feather is the "Notion-as-CMS for blogs" builder. It's narrower than Super or Potion — fewer customization knobs — but it still has a Custom Code surface.
- Feather dashboard → Settings → Custom Code (sometimes called Site Code depending on plan).
- Drop the CSS into Head.
- For the form itself, create a Notion page named "Contact" (or whatever you want it called publicly). In Feather's page-level settings, enable Allow HTML embeds, then paste the form into a Notion code block set to
html, identical to the Potion flow. - Publish. Feather rebuilds the static page on save.
Feather quirks to know
- Feather caches at the CDN edge. After republishing, you may need to clear your browser cache or wait 60–90 seconds for the form to appear.
- Feather's default font stack is more constrained than Super's. Use the CSS variable
--sf-fontin step 4 to inherit from the theme instead of hard-coding a font-family. - Feather doesn't expose a global "Body End" injection slot on the Starter plan. If you need site-wide CSS, the Head slot is fine — body-end is mostly for analytics scripts anyway.
Step 4: Style the form to match Notion's aesthetic
Notion's visual system is restrained: white surfaces, 1px borders in a near-gray, soft 4–8px radii, an Inter / IBM Plex Sans stack, and a single accent color. The CSS below mirrors that. Paste it into your builder's Head injection slot (Super: Code Injection → Head; Potion: Custom Code → Head; Feather: Custom Code → Head).
<style>
:root {
--sf-ink: #37352f;
--sf-ink-2: #6b6a64;
--sf-line: #e9e9e7;
--sf-bg: #ffffff;
--sf-accent: #2383e2; /* Notion blue. Swap for your brand. */
--sf-font: -apple-system, BlinkMacSystemFont, "Inter", "IBM Plex Sans", "Segoe UI", sans-serif;
}
.sf-form {
max-width: 560px;
margin: 32px 0;
display: flex;
flex-direction: column;
gap: 14px;
font-family: var(--sf-font);
color: var(--sf-ink);
}
.sf-form label {
display: flex;
flex-direction: column;
gap: 6px;
font-size: 14px;
font-weight: 500;
color: var(--sf-ink-2);
}
.sf-form input,
.sf-form textarea {
width: 100%;
padding: 10px 12px;
font-size: 15px;
font-family: inherit;
color: var(--sf-ink);
background: var(--sf-bg);
border: 1px solid var(--sf-line);
border-radius: 6px;
transition: border-color 120ms ease;
}
.sf-form input:focus,
.sf-form textarea:focus {
outline: none;
border-color: var(--sf-accent);
}
.sf-form button {
align-self: flex-start;
padding: 10px 18px;
font-size: 14px;
font-weight: 600;
color: #ffffff;
background: var(--sf-accent);
border: 0;
border-radius: 6px;
cursor: pointer;
}
.sf-form button:hover { filter: brightness(0.95); }
</style>That CSS is intentionally short. It uses Notion's actual ink color (#37352f), Notion's actual border color (#e9e9e7), and Notion's actual blue accent (#2383e2). Swap the accent to your brand color if your site uses one — every Notion site I've helped ship in the last year keeps these tokens and just changes that single value.
Step 5: Validation, spam protection, and mobile preview
The form in step 2 already has the three things that matter:
- HTML5 validation via
requiredon every field andtype="email"on the email input. The browser blocks submit until the format is right. No JavaScript needed. - Honeypot spam protection via the hidden
botcheckinput. Bots that scrape forms and submit them automatically fill every field they find, including invisible ones. Splitforms drops any submission wherebotcheckis non-empty. See the deeper breakdown in honeypot vs reCAPTCHA. - Server-side AI spam classification on top of the honeypot. You don't configure anything for this — it runs by default on every splitforms submission. Walkthrough: AI form spam detection.
Before you publish, preview on mobile. Notion site builders are good at responsive layout but the CSS above caps the form at 560px and switches to single-column on narrow screens automatically (because of flex-direction: column). Test these three things on a phone:
- Tap each input. The native keyboard should open the right type (email keyboard for the email field, default keyboard for name).
- The button should have at least 44px tap target height — the padding in step 4 gives you that.
- Submit a real test. The redirect should fire and land you on the thank-you page.
Step 6: Build a thank-you page and publish
The redirect hidden input in step 2 sends the visitor somewhere after submit. Create that page in Notion: a single H1 ("Thanks — we'll be in touch"), one paragraph ("Your message just landed in our inbox. Expect a reply within one business day."), and a link back to home. Publish it through your builder and copy the URL into the form's redirect attribute.
Then republish the contact page. Open it in an incognito window so you bypass cache. Submit a real message. Check three things, in order:
- The browser redirects to your
/thankspage within 1–2 seconds. - An email lands in your inbox (the one you signed up with on splitforms) within 5–10 seconds. Check Spam if you don't see it — first-time delivery sometimes routes there until your address has a reputation.
- The splitforms dashboard at splitforms.com/dashboard/submissions shows the row.
If all three pass, you're shipped. The form is now live, spam-protected, and emailing you on every submission. No plugin, no third-party iframe, no monthly add-on on top of your Notion builder bill.
Troubleshooting Notion-builder embed quirks
Most issues come from one of the three builders sandboxing your HTML differently. Here's what to check, in order:
- Form renders as plain text instead of a form (Potion / Feather). The code block's "Render HTML" toggle is off. Open the page settings in your builder and flip it on.
- Form is invisible / collapsed to zero height (Super). The parent column has
overflow: hidden. Wrap your form in<div style="min-height: 320px">as a quick fix, or use Super's full-width HTML block instead of an in-column embed. - Submit does nothing and the URL doesn't change. Almost always the
actionattribute is missing or misspelled. Open DevTools, inspect the form, confirmaction="https://splitforms.com/api/submit"is present. - splitforms returns 401. The access key is wrong, has whitespace, or your Allowed Domains setting doesn't include the published Notion site URL. Recopy the key and check the domain list in the splitforms dashboard.
- Submissions deliver but the email goes to spam. Your inbox doesn't trust the sender yet. Mark one as "not spam" and add the splitforms notification address to your contacts. For a deeper fix, see why contact form emails go to spam.
- The form looks fine on desktop but cramped on mobile. Your builder is injecting its own container padding inside the iframe. Add
.sf-form { padding: 0 16px; }to the head CSS to add breathing room only on the form's parent. - Honeypot is catching real users. A password manager auto-filled the hidden
botcheckcheckbox. Addautocomplete="off"(already in step 2's snippet) and confirm the field is hidden viadisplay:none, not justvisibility:hidden. - Notion design-system colors don't match yours. Your builder may have a custom theme overriding the CSS variables. Increase specificity: change
.sf-form inputto.notion-page .sf-form inputor wrap the form in an explicit class and target that.
Next steps and where to read more
- Send each submission into a Notion database row: send form submissions to Notion.
- Grab a ready-made template: free HTML contact form — pre-wired to splitforms with the styling from step 4.
- Moving from another builder? Try add a contact form to Framer or add a contact form to Webflow.
- Comparing form services before you commit: best free form backend services 2026, or check the splitforms vs Formspree breakdown.
- Lock down spam end-to-end: form spam protection complete guide.
- Reference: /docs, /api-reference, /faq, or browse all guides. Or skip ahead and get an access key.
FAQ
Can I add a real form directly inside Notion, without a site builder?
Not a real one. Notion's native form-style blocks (databases with form view, third-party widgets via /embed) can capture clicks, but they don't send email, don't run spam protection, and don't expose webhooks. The standard pattern in 2026 is: build the site visually in Notion, publish through Super.so, Potion.so, or Feather, then paste in an HTML form that posts to splitforms. That gives you a real backend without leaving the Notion editing flow.
Which Notion site builder makes embedding a form easiest?
Super.so is the most flexible — it has both a global Code Injection panel and per-page HTML embed blocks, so you can drop a form anywhere and style it once for the whole site. Potion.so works similarly with its Custom Code feature. Feather is built around blogs and newsletter pages, so its embed surface is a little narrower, but raw HTML blocks still work fine. If you don't have a builder yet and only need a contact form, Super is the safest pick.
Will the form's design clash with Notion's clean look?
Only if you paste in default browser styles and walk away. Notion's aesthetic is white background, IBM Plex / Inter style fonts, generous spacing, soft borders, and limited color. The CSS block in this guide matches that: white inputs, 1px light gray borders, 8px radius, system font, and a single accent color. Drop it in, change the accent to your site's brand color, and the form looks like it shipped with the theme rather than an iframe glued on.
Do I need JavaScript for the form to work?
No. The default splitforms flow is a plain HTML POST — the browser submits the form, splitforms responds with a redirect or a thank-you JSON payload, and the visitor lands on a confirmation page. That works in every Notion-published site, even ones with strict embed sandboxes. JavaScript is only needed if you want inline validation, AJAX submit without page reload, or a custom thank-you panel. Both modes are documented in the splitforms /docs.
How do I stop spam without a visible captcha?
Two layers: a hidden honeypot field named botcheck (covered in step 4) and splitforms' AI spam classifier on the server. The honeypot kills 90 percent of bots at zero UX cost — they fill every field they see, including invisible ones, so any submission with botcheck populated is dropped. The AI layer catches the rest by scoring message content. Visible captchas are usually unnecessary for a low-traffic Notion site and they hurt conversion on mobile.
Why does my Notion-embedded form show a white box / nothing at all?
Three usual causes. (1) You pasted into a plain Notion /embed block instead of your builder's HTML or code-injection panel — that block sandboxes iframes and strips raw HTML. (2) Your builder is loading the form inside an iframe and the parent CSS is hiding it with overflow:hidden on the section. (3) Content Security Policy on your builder is blocking the form action. The fix in all three cases is to use the builder's dedicated HTML / Custom Code feature, not Notion's embed primitive.
Can I keep my submissions in Notion as a database?
Yes — splitforms can post each submission to a Notion database via webhook. Set up a webhook in the splitforms dashboard pointing at a small serverless function (or a Zapier / Make zap) that calls the Notion API to insert a database row. We cover the full pattern in the /blog/send-form-submissions-to-notion guide. This way you keep the visual editor as your CMS and still get a real form backend.
What does this cost on splitforms versus the builder's built-in form?
splitforms is 1,000 submissions per month free, $5/mo Pro for 5,000, or $59 for 4 years on the discount plan. Super.so doesn't include a real form backend at all — you either use a third-party widget or paste in HTML. Potion.so and Feather are similar. The realistic comparison is splitforms versus paying Formspree or Getform on top of your builder subscription, where splitforms wins on both free tier and paid tier as of 2026-05.