Build Cognito Forms
YESreplaces $19/mosaves $228/yrback to the verdict
A form builder whose difficulty is not the fields but the contract around them: calculations evaluated identically on both sides with the server authoritative, submissions that survive retries and spam filters, and a payment that confirms from the provider's webhook rather than the browser.
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, the form pages and the test suite.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree
Why Entries, revisions and payments for one person fit in one file, and WAL lets the public form write while you read.
Get it Bundled with most runtimes; enable WAL on open. open ↗
Verify
PRAGMA journal_mode returns wal - accountfree to test
Why Webhook confirmation, duplicate events and out-of-order delivery only appear against a real provider flow.
Get it Sign up, use test keys, register a webhook, and install the CLI to forward events locally. open ↗
Verify
A test payment with the browser closed still marks an entry paid - accountfree tier or pennies
Why Received entries and the daily quarantine count go out by email, and a notification that never arrives is how a quarantined real entry stays unnoticed.
Get it Any provider with SMTP or an API key, and a verified sender.
Verify
A notification arrives for a test entry - have readyfree
Why The golden set and the CSV alignment test need schemas that changed over time and calculations with awkward rounding, which invented forms never have.
Get it Rebuild three forms you actually use, and change one after collecting a few entries.
Verify
The golden set covers all three and one form has two published versions - decidefree
Why A percentage discount and a hidden calculated field each have several defensible behaviours, and choosing after seeing the totals means choosing the flattering one.
Get it Write down how percentages round and whether hidden fields contribute to calculations.
Verify
Both rules are documented and asserted in the golden set
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- forms(id, slug, name, published_version, created_at, archived_at) - form_versions(form_id, version, schema_json, created_at) · every published schema kept, since entries reference the version they were filled against - entries(id, form_id, form_version, idempotency_key, state, created_at, archived_at) · state is received, quarantined, pending_payment, paid or complete - entry_revisions(entry_id, ordinal, answers_json, calculated_json, edited_by, at) · answers are never overwritten - uploads(id, entry_id, field_id, filename, bytes, sha256, stored_path, completed_at) - payments(id, entry_id, amount_minor, currency, provider_session_id, processed_event_ids_json, state, confirmed_at) - The idempotency key is unique per form, so a retried submission returns the existing entry rather than creating a second one.
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_PATH | required | ./data/forms.db | Entries, revisions and payments. Back it up with the uploads directory. |
UPLOAD_DIR | required | ./data/uploads | Files stored by content hash, referenced from entries once complete. |
STRIPE_SECRET_KEYsecret | required | sk_test_...redacted | Creates checkout sessions for the server's amount. |
STRIPE_WEBHOOK_SECRETsecret | required | whsec_...redacted | Entries confirm from webhooks, so an unverified endpoint marks entries paid. |
SMTP_URLsecret | required | smtp://user:pass@mail.example.com:587 | Notifications and the daily quarantine count. |
MIN_SUBMIT_SECONDS | required | 3 | Faster than this is quarantined, not dropped. Autofill is fast. |
ORPHAN_UPLOAD_HOURS | required | 24 | An upload no entry referenced after this is removed. |
TRUST_CLIENT_TOTAL | required | never | Present as a constant. The price is whatever the server computes. |
The build, in order
One engine for calculations and conditions
The browser and the server compute the same result from the same schema.
Imported by the browser bundle and the server alike.
Files
src/schema/evaluate.tssrc/schema/evaluate.test.tsA percentage of a float is a wrong price waiting to happen.
Files
src/schema/money.tsAt runtime it is an outage on a live form.
Files
src/schema/validate.ts
done when · tick each as it passeswatch out- Writing the calculation logic twice, once for the live preview and once for the server, produces two evaluators that agree in testing and disagree on the rounding of a percentage discount, and the customer is charged a total that is not the one they were shown. Share one module, and do arithmetic on integers.
Submissions that survive the network
A retry, a double tap or a crash after commit produces exactly one entry.
Generated when the form opens, sent on every attempt.
Files
src/public/form.tssrc/entries/create.tsTwenty parallel posts with one key produce one entry.
Files
tests/idempotency.test.tsAn entry must never point at a file that did not finish.
Files
src/uploads.ts
done when · tick each as it passeswatch out- A phone submits, the connection drops after the server committed, the browser shows an error, the person taps again, and there are two entries and later two charges. The idempotency key turns the second attempt into a read of the first.
Spam that cannot eat a real entry
Junk stays out of the inbox without a genuine submission ever being discarded.
With the reason, and a count the owner sees daily.
Files
src/spam/rules.tssrc/entries/quarantine.tsA honeypot a screen reader announces quarantines real submissions.
Files
src/public/honeypot.tsA refusal the person can read beats a silent discard.
Files
src/spam/ratelimit.ts
done when · tick each as it passeswatch out- Dropping a submission the filter dislikes discards the real customer who used autofill and submitted in four seconds, and nobody ever learns it happened. Quarantine with the reason, show the count, and make releasing one a single action.
Payment the server priced
The amount charged is computed on the server and the entry is paid only when the provider says so.
The posted total is discarded, tested by tampering with it.
Files
src/payments/price.tstests/tampered-total.test.tsSignature, event ids, and state derived from the entry.
Files
src/payments/webhook.tstests/webhook-order.test.tsThe webhook alone must be enough.
Files
tests/payment-tab-closed.test.tsterminalstripe listen --forward-to localhost:3000/api/payments/webhook
done when · tick each as it passeswatch out- The provider retries a webhook for up to three days and does not promise delivery order, so an integration that treats the first event as the whole story, or processes the same event twice, marks entries paid twice or never. Verify the signature, record processed event ids, and derive state from the entry rather than from which event came first.
Getting entries out, and telling other systemsproduct builder
Every entry leaves in a stable shape and nothing depends on this tool staying up.
Schema order shifts older rows silently.
Files
src/export/csv.tstests/csv-versions.test.tsFrom the entry's answers and calculated values, not a re-evaluation.
Files
src/documents/render.tsRecorded per attempt so a failure is visible rather than silent.
Files
src/webhooks/out.ts
done when · tick each as it passeswatch out- Exporting columns in the current schema's order silently misaligns entries filled before a field was renamed or inserted, and the spreadsheet looks fine while every older row is shifted by one. Key columns by field id and include a mapping row.
That is the whole plan for Cognito Forms. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Compliance certifications and processing agreements.
- Multi-user approval workflows.
- A third-party challenge service by default.
- Hundreds of integrations.
- compliance controls and guaranteed delivery
- enterprise workflow logic
- spam and abuse defense at scale
- large integration and template catalog
- Save and resume for long forms, which the idempotency key already half provides
- Repeating sections with calculations over their rows, the feature that separates a form tool from a survey tool
- Conditional notification recipients, which is the first workflow anyone actually asks for
Need the files? The project pack on the verdict page hands your agent the whole brief · more forms.