Why this migration is even a question
Typeform is a UX-first product. The whole pitch is the conversational, one-question-at-a-time form experience with smooth transitions, branching logic, and a polished embed widget. For survey designers, marketers, and anyone whose primary KPI is completion rate, that experience is worth real money.
For developers building actual forms — contact pages, lead capture, simple sign-up flows, internal tools — Typeform's pricing doesn't scale. The free tier caps at 10 responses per month with a maximum of 10 questions. The Basic plan is $25/month for 100 responses. Plus is $50/monthfor 1,000. By the time your form hits 5,000 submissions/month — which any moderately popular contact form does — you're on a Business plan north of $80.
Compare that to a plain HTML form pointed at a backend that costs $5/month flat. The math gets uncomfortable fast. Typeform's pricing is calibrated to companies running their entire customer-facing onboarding through it. If you're not running customer-facing onboarding through it, you're overpaying for a UX layer you don't need.
What you'll lose by leaving Typeform
Be honest about this list. If any of these matter to you, don't migrate — Typeform is the right tool for that job.
- Conversational, one-question-at-a-time UX. Typeform's signature feature. The smooth question-by-question flow is genuinely good for completion rates on long surveys.
- Branching / logic jump. Typeform's visual logic editor lets a non-developer build "if answer to Q3 is Yes, skip to Q7" flows. Replicating that in plain HTML means writing JavaScript.
- Drag-and-drop form builder. Typeform's editor is the gold standard. splitforms expects you to write HTML.
- Built-in templates with sophisticated logic. Quizzes, NPS surveys, lead scorers — Typeform has thousands of pre-built templates. splitforms has zero.
- Calendar booking field. Typeform has a native calendar/scheduling field. splitforms doesn't — you'll embed Cal.com or SavvyCal instead.
- Stripe payment field. Typeform's payment field collects credit cards inline. splitforms doesn't — you'll redirect to a Stripe Payment Link.
- File upload UI. Typeform has a polished file-upload widget with progress bars and previews. splitforms supports file fields but the UI is whatever your HTML provides.
- Embedded form widget. Typeform has a one-line JavaScript embed that loads its full widget — pop-up, slide-in, full-page modes. With splitforms you're embedding plain HTML.
- Webhook integrations marketplace. Typeform has hundreds of pre-built integrations (HubSpot, Salesforce, Mailchimp, etc.) wired into its dashboard. splitforms ships signed webhooks plus a Zapier integration; everything else is DIY.
Honest disclosure: if you genuinely need conversational forms with branching, splitforms is not the right fit. Stay on Typeform, or look at Tally, which is a much closer Typeform clone (same UX, much cheaper). This guide is for people whose Typeform is doing the job of a regular HTML form and is being charged Typeform money for it.
What you'll gain
The other side of the trade. If you don't need conversational UX, here's what you pick up:
- ~50× cheaper at scale. $5/month covers 5,000 submissions on splitforms. The equivalent volume on Typeform is the Plus plan at $50/month — and even that maxes out at 1,000 responses; for 5,000 you're on Business at $83/month.
- Unlimited forms on every plan. Typeform's plans cap the number of active forms (Basic = 1, Plus = unlimited but still per-response limits). splitforms gives you unlimited forms on the free tier.
- Full HTML control. No Typeform branding on free-tier embeds, no widget overhead, no third-party JavaScript loading on your page. Your form is your HTML.
- Real API access on the free tier. Typeform's API is paywalled behind paid plans. splitforms' API works on day one with a free access key.
- Signed webhooks with retry + dead-letter. Typeform's webhook is single-shot — if your endpoint is down, the event is dropped. splitforms retries with exponential backoff and persists failed deliveries.
- AI spam classifier. Typeform uses keyword filters and reCAPTCHA. splitforms ships a transformer-based spam classifier on every plan including free.
- MCP server for AI agents. splitforms exposes form submissions over the Model Context Protocol so Claude / Cursor / your custom agent can read them directly. Typeform has no equivalent.
- Open-source-friendly. Free-tier shows a small "powered by splitforms" attribution; Typeform's free tier is heavily branded with widget chrome and a "Made with Typeform" logo on every form.
See the full feature breakdown on splitforms vs Typeform.
Pricing comparison
The starkest difference between Typeform and splitforms is pricing per submission. Typeform charges per response because the response itself is the unit of value (sophisticated UX collected the response). splitforms charges flat per tier because submissions are cheap to receive — what costs money is storage, deliverability, and spam filtering.
| Tier | Typeform | splitforms |
|---|---|---|
| Free | 10 responses/mo, 10 questions max, Typeform branding | 1,000 submissions/mo, unlimited forms, light attribution |
| Entry-paid | $25/mo Basic — 100 responses/mo | $5/mo Pro — 5,000 submissions/mo |
| Mid-paid | $50/mo Plus — 1,000 responses/mo | n/a — Pro covers this band |
| High-paid | $83/mo Business — 10,000 responses/mo | $59 4-Year — 15,000/mo for 48 months (~$1.23/mo) |
At 5,000 submissions/month — typical for a moderately busy contact form — Typeform costs $83/month and splitforms costs $5/month. Annualized that's $996 vs $60. The 4-Year plan amortizes to about $15/year for the same volume.
The 3-step migration
The migration itself is small. The hard part is rebuilding any branching logic — covered later. For a basic contact form or single-page survey, this is what you do:
Step 1: Build the splitforms equivalent
Sign in at splitforms.com/login, copy your auto-generated access key, and create a plain HTML form that mirrors your Typeform's questions. Use the same field names wherever you can — that makes downstream remapping (Zapier, Sheets) much simpler.
<!-- splitforms equivalent of a Typeform contact form -->
<form action="https://splitforms.com/api/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<label>What's your name?
<input type="text" name="name" required />
</label>
<label>Work email?
<input type="email" name="email" required />
</label>
<label>What can we help with?
<textarea name="message" required></textarea>
</label>
<!-- Honeypot -->
<input type="checkbox" name="botcheck" style="display:none" tabindex="-1" />
<button type="submit">Send</button>
</form>Deploy this on a hidden URL — /contact-v2is the convention. You don't need to make it public yet. You just need it live somewhere you can hit it from.
Step 2: Run shadow mode for 7 days
Both forms live in parallel. Drive 5-10% of your contact-page traffic to /contact-v2 via a query-string toggle, an A/B test, or simply linking it from a few non-critical pages (footer, FAQ).
On Vercel, the easy version is a middleware that flips a cookie based on a hash of the visitor IP:
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(req: NextRequest) {
if (req.nextUrl.pathname !== "/contact") return NextResponse.next();
const seed = req.ip ?? req.headers.get("x-forwarded-for") ?? "0";
// 10% of traffic to /contact-v2
if (hash(seed) % 100 < 10) {
return NextResponse.rewrite(new URL("/contact-v2", req.url));
}
return NextResponse.next();
}
function hash(s: string) {
let h = 0;
for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0;
return Math.abs(h);
}For 7 days, compare submission counts, spam rate, and reply quality between the two forms. If splitforms' numbers track Typeform's within ±10%, you're safe to cut over.
Step 3: Switch over
- Update your primary
/contactpage to render the splitforms HTML directly (delete the Typeform embed). - Search your repo for the Typeform embed snippet (
grep -r "typeform.com" .) and replace each occurrence. - Update internal links pointing to standalone Typeform URLs (
yourname.typeform.com/to/abc123). - Export your historical Typeform responses to CSV — keep the file for 30 days as a rollback safety net.
- Disable, then archive, your Typeform account at the end of the 30-day window.
Total active work: about 30 minutes for a single form, longer if you have many embeds. The 7-day shadow mode is mostly waiting.
Code examples
Before: Typeform embed
<!-- Typeform's standard widget embed -->
<div data-tf-widget="abc123XY" data-tf-opacity="100"
data-tf-iframe-props="title=Contact us"
data-tf-transitive-search-params
data-tf-medium="snippet"
style="width:100%;height:500px"></div>
<script src="//embed.typeform.com/next/embed.js"></script>That loads Typeform's ~120KB widget script, renders an iframe pointed at typeform.com, and submits responses through Typeform's infrastructure. Your page is now dependent on a third-party origin loading correctly.
After: splitforms HTML form
<!-- splitforms — plain HTML, no third-party scripts -->
<form action="https://splitforms.com/api/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<input type="hidden" name="redirect" value="https://yoursite.com/thanks" />
<label>Name <input name="name" required /></label>
<label>Email <input type="email" name="email" required /></label>
<label>Message <textarea name="message" required></textarea></label>
<input type="checkbox" name="botcheck" hidden tabindex="-1" />
<button type="submit">Send</button>
</form>React + react-hook-form
If you're on a React stack and want client-side validation:
"use client";
import { useForm } from "react-hook-form";
type Fields = { name: string; email: string; message: string };
export function ContactForm() {
const { register, handleSubmit, formState: { isSubmitting, isSubmitSuccessful } } = useForm<Fields>();
const onSubmit = async (data: Fields) => {
const res = await fetch("https://splitforms.com/api/submit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
access_key: process.env.NEXT_PUBLIC_SPLITFORMS_KEY,
...data,
}),
});
if (!res.ok) throw new Error("submit failed");
};
if (isSubmitSuccessful) return <p>Thanks — we’ll be in touch.</p>;
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name", { required: true })} placeholder="Name" />
<input {...register("email", { required: true })} type="email" placeholder="Email" />
<textarea {...register("message", { required: true })} placeholder="Message" />
<button disabled={isSubmitting}>Send</button>
</form>
);
}Webflow custom-code embed
If your site is on Webflow, drop this into a custom-code embed block:
<form action="https://splitforms.com/api/submit" method="POST"
class="w-form" style="display:flex;flex-direction:column;gap:12px">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY">
<input type="hidden" name="redirect" value="https://yoursite.com/thanks">
<input type="text" name="name" placeholder="Name" required>
<input type="email" name="email" placeholder="Email" required>
<textarea name="message" placeholder="Message" required></textarea>
<input type="checkbox" name="botcheck" style="display:none" tabindex="-1">
<button type="submit" class="w-button">Send</button>
</form>That replaces the Typeform embed block entirely. Style it with your existing Webflow classes — no extra script tags needed.
Replacing Typeform's killer features
The features people actually use, and how to rebuild them on splitforms:
Branching logic
In Typeform you draw branches in a visual editor. In plain HTML you write a few lines of JavaScript that show or hide fields based on previous answers:
<select name="role" onchange="this.form.dataset.role = this.value">
<option value="">Pick one</option>
<option value="dev">Developer</option>
<option value="designer">Designer</option>
</select>
<!-- Only shown if role=dev -->
<label data-show-if="role=dev">
Favorite language
<input name="language" />
</label>
<style>
form:not([data-role="dev"]) [data-show-if="role=dev"] { display: none; }
</style>That's the entire pattern. Scale it up for multi-branch logic; for forms with more than ~5 conditional fields, factor it into a small React or Alpine.js component instead.
Calendar booking
splitforms doesn't have a built-in calendar field. Embed Cal.com or SavvyCal next to your form, or link to a booking page on the thank-you redirect:
<input type="hidden" name="redirect"
value="https://cal.com/your-handle/intro?prefill_name=$name&prefill_email=$email" />splitforms substitutes $fieldname tokens in the redirect URL with the submitted values, so the booking page can pre-fill name and email automatically.
Payment fields
Typeform's Stripe payment field collects card details inline. splitforms doesn't do this — you collect the contact details and redirect to a Stripe Payment Link or Stripe Checkout session for the actual payment. That's how most production payment flows work anyway: keeping card capture on Stripe's domain reduces your PCI scope.
File upload
splitforms supports <input type="file">fields with a per-form size cap. The UI is whatever your HTML provides — there's no built-in progress bar widget. For multiple-file or large-file uploads, point users at a Tally form or a dedicated uploader (UploadThing, Filestack) and capture the resulting URL in your splitforms submission.
Webhook marketplace
Typeform has a marketplace of pre-built integrations. splitforms ships signed webhooks with HMAC-SHA256, exponential-backoff retries, and a dead-letter queue, plus first-party Slack and Discord delivery, plus Zapier. For anything not on that list, the webhook hits your endpoint and you take it from there.
Common migration gotchas
- Typeform field IDs vs your new field names. Typeform stores responses keyed by IDs like
4f1a2b3c, not by the human-readable question label. Your new HTML form uses plain names likeemailandcompany. Anywhere downstream that consumes the data — a Google Sheet, a Notion DB, a Zapier zap, your CRM — will receive a different key shape after migration. Re-map column headers before you cut over, or your Sheets row will land in the wrong columns on day one. - Existing Zapier zaps.Typeform-side Zaps are triggered by Typeform's webhook. splitforms has its own Zapier app — create new Zaps pointing at splitforms with the same downstream actions, run both old and new in parallel for a week, then disable the Typeform-side ones. Don't delete the old Zaps until you've confirmed the new ones fire.
- Existing Sheets / Notion sync.If Typeform was syncing responses into a Google Sheet or Notion DB via its native integration, that sync stops the moment you cut over. splitforms has its own integrations — recreate the sync against the same destination Sheet / Notion DB. The historical Typeform rows stay; new rows arrive via splitforms' integration with the new field-name shape.
- Embedded forms in third-party tools. If your Typeform is embedded in Notion, Help Scout, Intercom, or a no-code site builder, search those tools too — not just your codebase. Typeform embeds can hide in places
grepwon't find. - Don't archive Typeform too fast.Keep the account active for 30 days after cutover. Some browsers will have your Typeform page cached; some users have stale tabs open. Running both for a month costs nothing on Typeform's free tier.
FAQ
Why move from Typeform to splitforms?
Three reasons. (1) Pricing: Typeform's free tier caps at 10 responses/month and the entry paid plan is $25/mo for 100 responses. splitforms gives you 1,000 free, then $5/mo for 5,000. (2) Developer ergonomics: splitforms is plain HTML POST + a real API + signed webhooks. Typeform is a managed widget you embed. (3) No vendor lock-in: your form is regular HTML you can move anywhere. If you don't need conversational UX, you're paying for features you don't use.
Will I lose my form's UX?
Yes — you lose Typeform's conversational, one-question-at-a-time experience and the drag-and-drop builder. splitforms is a backend for plain HTML forms. If conversational UX is the reason your visitors complete your form (lead-gen, surveys with high drop-off), don't migrate. If your Typeform is mostly a contact form or simple survey, you'll never miss it.
How long does the migration take?
About 30 minutes for a single contact form, 2-4 hours for a multi-step form with branching logic. The longest part is rebuilding any conditional branching in plain JavaScript — Typeform handles that visually, splitforms expects you to write the conditions yourself.
Can I keep my Typeform fields?
Field names yes, field IDs no. Typeform uses internal IDs like `entry.4f1a2b3c` while your new form will use plain HTML names like `email` or `company`. Re-map them in your downstream tools (Zapier, Sheets, CRM) so the new field names land in the right columns. Export your Typeform CSV first as a backup.
What about my existing webhooks/Zapier zaps?
You'll need to relink them. Typeform's Zapier integration listens to Typeform's webhook; splitforms has its own Zapier integration and signed webhook. Create new Zaps pointing at splitforms, run both forms in parallel for a week, then disable the Typeform-side Zaps. Same flow for Slack, Discord, and custom webhooks.
Is there a free Typeform alternative?
splitforms is genuinely free for 1,000 submissions/month with unlimited forms. Tally is free for unlimited responses but with Tally branding. Google Forms is free but ugly. If you want Typeform's conversational UX without the price, Tally is your closest match. If you want a developer-grade form backend behind plain HTML, splitforms is the cheapest option that doesn't paywall webhooks.
Does splitforms support payment forms?
Not natively as a Typeform-style payment field. The pattern is: collect contact details with splitforms, then redirect to a Stripe Payment Link or Checkout session. That's actually how most production payment flows work anyway — the form-builder payment field is a convenience, not a requirement.
Can I run both during the cutover?
Yes, and you should. Keep your Typeform live, deploy the splitforms version on a separate URL (like /contact-v2), and either A/B test or send 5-10% of traffic via a query-string toggle. Compare submission quality and spam rate over 7 days. Once splitforms looks healthy, switch your primary form's URL and archive the Typeform.