Build Cognito Forms

YESreplaces $19/mosaves $228/yrback to the verdict

0%0 of 18 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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.

estimated effort weekendthe files for this build are in the project pack

RuntimeTypeScript with SQLite in WAL modeSchemaA JSON form definition with fields, conditions and calculationsEvaluationOne expression engine shared by browser and server, money in integer minor unitsSubmissionClient-generated idempotency keys, entries appended and revisedPaymentHosted checkout with the amount computed server side, confirmed on webhook

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. 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

  2. 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

  3. free 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

  4. free 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

  5. 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

  6. 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.

VariableNeededExampleWhere the value comes from
DATABASE_PATHrequired./data/forms.dbEntries, revisions and payments. Back it up with the uploads directory.
UPLOAD_DIRrequired./data/uploadsFiles stored by content hash, referenced from entries once complete.
STRIPE_SECRET_KEYsecretrequiredsk_test_...redactedCreates checkout sessions for the server's amount.
STRIPE_WEBHOOK_SECRETsecretrequiredwhsec_...redactedEntries confirm from webhooks, so an unverified endpoint marks entries paid.
SMTP_URLsecretrequiredsmtp://user:pass@mail.example.com:587Notifications and the daily quarantine count.
MIN_SUBMIT_SECONDSrequired3Faster than this is quarantined, not dropped. Autofill is fast.
ORPHAN_UPLOAD_HOURSrequired24An upload no entry referenced after this is removed.
TRUST_CLIENT_TOTALrequiredneverPresent as a constant. The price is whatever the server computes.

The build, in order

  1. One engine for calculations and conditions

    The browser and the server compute the same result from the same schema.

    1. Imported by the browser bundle and the server alike.

      Files src/schema/evaluate.tssrc/schema/evaluate.test.ts

    2. A percentage of a float is a wrong price waiting to happen.

      Files src/schema/money.ts

    3. At runtime it is an outage on a live form.

      Files src/schema/validate.ts

    done when · tick each as it passes
    watch 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.
  2. Submissions that survive the network

    A retry, a double tap or a crash after commit produces exactly one entry.

    1. Generated when the form opens, sent on every attempt.

      Files src/public/form.tssrc/entries/create.ts

    2. Twenty parallel posts with one key produce one entry.

      Files tests/idempotency.test.ts

    3. An entry must never point at a file that did not finish.

      Files src/uploads.ts

    done when · tick each as it passes
    watch 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.
  3. Spam that cannot eat a real entry

    Junk stays out of the inbox without a genuine submission ever being discarded.

    1. With the reason, and a count the owner sees daily.

      Files src/spam/rules.tssrc/entries/quarantine.ts

    2. A honeypot a screen reader announces quarantines real submissions.

      Files src/public/honeypot.ts

    3. A refusal the person can read beats a silent discard.

      Files src/spam/ratelimit.ts

    done when · tick each as it passes
    watch 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.
  4. Payment the server priced

    The amount charged is computed on the server and the entry is paid only when the provider says so.

    1. The posted total is discarded, tested by tampering with it.

      Files src/payments/price.tstests/tampered-total.test.ts

    2. Signature, event ids, and state derived from the entry.

      Files src/payments/webhook.tstests/webhook-order.test.ts

    3. The webhook alone must be enough.

      Files tests/payment-tab-closed.test.ts

      terminal
      stripe listen --forward-to localhost:3000/api/payments/webhook
    done when · tick each as it passes
    watch 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.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more forms.