- Aug 30, 2026
- 5 min read
Replacing a Formbricks Qualifier with My Own Form
My consulting site sells a paid 30-minute decision call. The rule in the terms is “qualifier first”: tell me the decision you’re facing before you book. For months that qualifier was a hosted Formbricks survey. Every call-to-action on the page — the hero button, two of the three offer cards, a “sign up for upcoming talks” link — opened Formbricks in a new tab.
It worked. But every other lead from my sites now goes through one central forms Worker into one inbox, with alerts, double opt-in and retention handled in one place. The qualifier was the exception: answers lived in Formbricks, and I’d have to sync them back to know who had asked what.
Own form vs Formbricks
| Own form → central Worker | Formbricks | |
|---|---|---|
| One list | Lands with every other lead: alert, weekly digest, backup, erasure | Separate dashboard; needs a webhook to join the list |
| Privacy | No third-party script or processor | A third-party script and processor to disclose |
| Without JavaScript | Plain <form method="post"> still works | Doesn’t |
| Consent | Optional updates box goes through the same double opt-in | Possible, via mapping |
| Look | Exactly the site’s design | Their UI |
| Changing questions | A code change + deploy | Edit in a dashboard |
| Analytics | Counts only | Per-question drop-off |
The last two rows are the honest case for Formbricks. For a qualifier with five fixed questions that I don’t plan to A/B test, they didn’t outweigh the first five.
Keep the visitor on the page
The biggest UX change wasn’t the form, it was the links. Every external Formbricks URL became an anchor:
- “Qualify in 60 sec”, “See if you’re a fit”, the hero button →
#qualify - “Sign up for upcoming” (talks) →
#updates, a small newsletter box under the qualifier
The qualifier section sits directly above the booking calendar. Nobody leaves the site, and the next step is the next thing on screen.
The offer cards also pre-select the tier. Each card’s link carries a
data-tier, and a few lines of script set the select before the jump:
<a href="#qualify" data-tier="Monthly Retainer" class="btn-ghost">See if you're a fit</a>
document.querySelectorAll<HTMLAnchorElement>("a[data-tier]").forEach((link) =>
link.addEventListener("click", () => {
const select = document.getElementById("q-tier") as HTMLSelectElement | null;
if (select && link.dataset.tier) select.value = link.dataset.tier;
}),
);
Without JavaScript the anchor still scrolls; the visitor just picks the tier themselves.
The form
Five questions plus contact details, all in the site’s own markup:
- Which offer: decision call, monthly retainer, not sure yet
- The decision or problem (required, free text)
- Stage, budget bracket, timeline: three short selects
- Name and role (optional), email (required)
- An optional, unticked “also send me occasional updates” box
- A honeypot field for dumb bots
It posts same-origin to /api/forms/submit with kind=qualifier. With
JavaScript it sends JSON and shows the result inline; without it, the browser
posts the form and the Worker redirects back with ?form=sent (or
confirm, or error). On success the message is the funnel:
Thanks — got it. Next: book your call below ↓
What the Worker does with it
The Worker got a new qualifier kind for the consulting site’s registry
entry: the free text is required, the five answers are stored as a small
qualification object, and the alert on my phone summarises them without
personal data — offer, budget and timeline, plus the email domain only.
A question about “a vector DB for our RAG” from a CTO shows up as roughly
“Decision Call, budget 1–5k, This month · from @startup.io”.
If the updates box is ticked, the lead goes through the same double opt-in with signed links as every newsletter: the form says “confirm via the email I just sent”, and nothing else is sent until they do. Unticked, they only ever hear from me about their question.
The policy had the better promise
The site’s existing privacy policy said: qualifier answers from people who don’t book are deleted within 90 days. My first version of the Worker kept qualifiers for 24 months, like enquiries.
I changed the code, not the promise: qualifier now expires after 90 days in
KV (bookings are their own record and keep their own retention). Same lesson
as when
the privacy page found a retention bug:
write the policy against the code, and when the policy is better, fix the
code.
Nothing thrown away
The Formbricks setup — the Python script that created the surveys and the
survey definitions as JSON — is still in the repo, untouched. I also tagged
the last commit before the migration as pre-lead-hub, so the old version of
the page is one git checkout away if I ever want the survey back.
I tested the new section in a browser against the built site with mocked Worker answers (plain, double opt-in, and the no-JS return), the same way as in testing form states with Playwright, then once for real from my own address: alert on the phone, confirmation email in the inbox.
Takeaways
- One list beats a nicer survey UI when everything else already flows into one place.
- Turn external CTAs into anchors; put the next step (the calendar) directly after the form.
- Pre-select with
data-*attributes; it degrades to a plain anchor. - Optional consent stays optional and unticked, and goes through double opt-in.
- Alerts summarise, never quote: offer, budget, timeline, domain.
- When the policy promises less retention than the code keeps, change the code.
- Keep the old integration in the repo and tag the pre-change state.
Next: Cal.com bookings into your own pipeline with a signed webhook.