Build Fillout
YESreplaces $19/mosaves $228/yrback to the verdict
A form builder where the interesting part is the logic engine: questions that appear based on earlier answers, validation that runs the same rules on both sides, partial responses that survive a closed tab, and a submission pipeline that never loses an answer because a webhook failed.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the server and shares the logic evaluator with the client.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree locally
Why Versioned questions, keyed answers and an outbox all want a real relational database.
Get it Docker or your package manager. open ↗
Verify
psql connects and creates a table - accountfree to test; per-transaction in production
Why Phase 4 only. Hosted Checkout keeps card details off your server entirely.
Get it Sign up, use test keys, and configure a webhook endpoint with its signing secret. open ↗
Verify
A test Checkout session completes and the webhook is received and verified - installfree
Why Both payment and delivery testing need real webhooks reaching your machine.
Get it The Stripe CLI forwards events, or use a tunnelling tool.
Verify
A test event arrives at your local endpoint and verifies - decidefree
Why Retention is stated on the form and enforced by a job, and the two have to match. Some question types make a form collect data with a much higher duty of care.
Get it Decide the period, decide what you will refuse to collect, and write both down before building.
Verify
A written note states the period and the categories you will not collect
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- forms(id, slug, title, status, version, published_at) · a published version is immutable and edits create a new version - questions(id, form_id, form_version, key, type, label, required, config_json, ordinal) · key is stable across versions, id is not - rules(id, form_id, form_version, target_question_id, condition_json, action) · action is show, hide or require - responses(id, form_id, form_version, started_at, submitted_at, resume_token_hash, ip_country) - answers(response_id, question_key, value_json, at) · keyed by the stable key so a response stays readable after the form changes - outbox(id, response_id, kind, payload_json, attempts, next_attempt_at, delivered_at, last_error) - A response records the form version it was submitted against, because reading old answers against a new form is how reporting goes wrong.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_URLsecret | required | postgres://localhost:5432/forms | Contains respondents' answers. Treat the connection string and backups accordingly. |
RESUME_TOKEN_TTL_HOURS | required | 72 | How long a resume link works. It is a bearer credential, so shorter is safer. |
RESPONSE_RETENTION_DAYS | required | 365 | Must match what the form tells respondents. |
STRIPE_SECRET_KEYsecret | optional | sk_test_... | Only for paid forms. Test keys until you are certain the webhook path is right. |
STRIPE_WEBHOOK_SECRETsecret | optional | whsec_... | Verifies incoming events. An unverified webhook endpoint is an open endpoint. |
WEBHOOK_SIGNING_KEYsecret | required | a random 32-byte hex string | Signs your outgoing webhooks so receivers can verify them. |
The build, in order
Questions, answers and versions
A form collects answers that stay readable after the form changes.
The key is what an answer refers to and it must not change when the question is edited.
Files
src/schema.sqlEditing a live form mid-collection is otherwise how responses become inconsistent.
Files
src/versions.tsReading old answers against a new form is a reporting bug waiting to happen.
Files
src/responses.ts
done when · tick each as it passeswatch out- Storing answers as a JSON blob against the current form means renaming or deleting a question silently breaks every historical response, and this is discovered months later when someone runs a report. Store answers as rows keyed by a stable key and record the form version.
Conditional logic that runs on both sides
Questions appear based on earlier answers, and the server agrees.
Shared module. Two implementations diverge and the divergence is a security hole.
Files
src/logic/evaluate.tssrc/logic/evaluate.test.tsReject answers to hidden questions. Test with a crafted post, not the browser.
Files
src/submit.tsStatic analysis of the rule tree. A cycle at runtime hangs a respondent.
Files
src/logic/analyse.ts
done when · tick each as it passeswatch out- Logic evaluated only in the browser means a submitted response can contain answers to questions that should have been hidden, or omit ones that should have been required, because anyone can post directly to the endpoint. Share one evaluator and run it server-side on submission.
Partial responses that survive a closed tab
Someone answers half a long form, comes back tomorrow and continues.
Per answer, not per page. A long form loses more than a page's worth otherwise.
Files
src/partial.tsIt is a bearer credential for personal information. Treat it like one.
Files
src/resume.tsWhich questions were visible, not just the values.
Files
src/partial.ts
done when · tick each as it passeswatch out- A resume link is a bearer credential granting access to whatever has already been answered, which on many forms is personal information. Store a hash rather than the token, keep the expiry short and stated, and never put the token in a page the respondent might share.
Taking payment inside a form
A form can charge, and a paid submission is never lost.
Card details on your server is a different project with a compliance scope.
Files
src/payments.tsNot the redirect. Test by killing the browser after paying.
Files
src/webhooks.tsKeyed on the event id. Retries are normal.
Files
src/webhooks.tstests/webhook-idempotency.test.ts
done when · tick each as it passeswatch out- Marking the response complete on the browser redirect loses submissions whenever someone pays and closes the tab before the redirect completes, which is a paid customer with no record of their answers. The webhook is the source of truth and the redirect is only a convenience.
Getting answers where they need to go
Notifications and webhooks fire reliably, and a failure never loses a submission.
The response and the intent to deliver commit together or not at all.
Files
src/outbox.tsNever in the request path. The respondent should not wait on someone else's server.
Files
src/worker.tsA timestamped signature the receiver can verify.
Files
src/sign.ts
done when · tick each as it passeswatch out- Calling a webhook inside the submission request means a slow or failing endpoint either delays the respondent or loses the delivery entirely when the request times out. Commit the response and the delivery intent together, then deliver separately.
Exports, retention and what you promisedproduct builder
Answers come out cleanly and are deleted when they should be.
Empty cells where a question did not exist. Dropping columns silently loses data.
Files
src/export.tsAnswers, partials, outbox payloads. A payload in the outbox is a copy of the response.
Files
src/deletion.tsIf you tell respondents how long you keep answers, the job has to actually run.
Files
src/retention.ts
done when · tick each as it passeswatch out- A CSV export that only includes the current version's questions silently drops answers to questions that were removed, and nobody notices because the file looks complete. Export the union of questions across the versions in range, with empty cells where a question did not exist.
That is the whole plan for Fillout. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- The visual builder that makes forms pleasant to construct.
- Templates and themes.
- Integrations with dozens of destinations.
- Certified accessibility work.
- excellent Airtable and Notion integrations, scheduling, payments, and hosted polish
- advanced branching
- payment and CRM integrations
- large template libraries
- enterprise compliance
- Scheduling and quotas, which close a form on a condition rather than by hand
- Response analytics per question, which is where abandonment becomes actionable
- Multi-page forms with saved progress per page, once partials are trustworthy
Need the files? The project pack on the verdict page hands your agent the whole brief · more forms.