A visual, no-code style form builder with a live designer, a runner for respondents, and a results view with spreadsheets. Design a form, preview it exactly as respondents see it, share a link, and collect submissions — served by a small Express API.
Built with Angular 22 + Angular Material 22 on the frontend and a TypeScript Express backend with TypeORM persistence.
- Visual form designer — build multi-page forms by dragging nothing at all: pick a field from the palette and tune it in the property panel.
- 16 field types
- Basic (10): short text, long text, number, yes/no, single choice, dropdown, multiple choice, date, time, date-time
- Advanced (3): rating scale, file upload, signature pad
- Layout (3): section heading, question group, text display
- Live runner — respondents fill the form with real Material controls, page-by-page navigation, and validation with per-field messages.
- Share links — copy a public
/runner/:idlink straight from the landing page; after submit, respondents get a thank-you screen with a downloadable PDF receipt. - Results — every submission stored server-side; view them side by side and export to CSV, Excel (xlsx), or a PDF summary (or email a summary), and delete single submissions or clear all.
- Page logic — show or hide whole pages via a visual condition builder ("all / any" conditions, nested groups, literals, ranges, and references to other fields).
- Field visibility — every field can show/hide conditionally, again with the same visual condition editor.
- Default values — static values, computed expressions (
now(),concat('A','B')), or copy the value from another field. - Calculated fields — number fields with formulas over other fields (e.g.
(a * b) / 100) and configurable decimals. - Validation rules — required, length bounds, min/max, between, regex patterns, date ranges, integers, email/url/phone, file-type and file-count/size rules, plus fully custom expressions — each with a custom error message.
- Groups — nest questions inside collapsible groups.
- Field layout — full / half / third widths so multi-column forms are possible.
- Uploads & signatures — file fields enforce
accept/multiple/size/file-type rules; signatures are captured on a device-pixel-correct signature pad. - Markdown titles & descriptions — question labels, page titles/subtitles and descriptions are rendered as Markdown (bold, italic,
code, lists, links) withmarkdown-it; block formatting (paragraphs, lists, headings) works in descriptions. - Draft autosave — a respondent's answers are restored if they reload, cleared on submit or reset.
- Import / export — save forms to JSON and re-import them (with schema validation) to continue editing.
- i18n — the whole UI ships in English and German (auto-detected, or toggled manually).
- Persistence — forms, submissions, users, and durable sessions are stored server-side through TypeORM. SQLite is the default; MySQL is supported through configuration. The browser keeps short-lived cached API responses and local respondent drafts to reduce repeated requests; it never treats that cache as an offline write store.
The Express API (src/server/) exposes:
POST /api/auth/login,POST /api/auth/register— email/password login and self-registration. Self-registration is disabled by default (403); setALLOW_REGISTRATION=trueto enable it. New registrations receive theeditorrole; sessions are durable, expire after 24 hours, and bearer tokens are stored as hashes. Bothemailandpasswordare required.GET/POST /api/users,PATCH /api/users/:id— administrator-only user provisioning; the patch updates email, role and/or password (a new password ends that user's sessions). Forms are isolated by owner; admins retain access to all forms.GET /api/forms,GET/PUT/DELETE /api/forms/:id— form CRUD.POST /api/formscreates an empty form ({ pageTitle? }),POST /api/forms/importimports a full form definition as a new form.GET/POST/DELETE /api/forms/:id/submissions[ /:submissionId]— submissions per form.GET /api/health— liveness probe.
The first start creates an admin account admin@formmaker.local (override with FORMMAKER_ADMIN_EMAIL). Its password comes from FORMMAKER_PASSWORD; if that is unset or shorter than 8 characters, a random password is generated and printed to the server log.
SQLite is used by default at src/server/data/formmaker.sqlite. To use MySQL instead, set DATABASE_PROVIDER=mysql and DATABASE_URL=mysql://user:password@host:3306/formmaker. The schema is managed by TypeORM migrations in src/server/migrations (synchronize is never used). Pending migrations run automatically on startup; set TYPEORM_MIGRATIONS_RUN=false to apply them yourself with npm run migration:run (also migration:revert and migration:generate -- src/server/migrations/<Name>). Databases created before migrations existed are picked up automatically by the baseline migration.
- Angular 22 (zoneless change detection, signals, new control flow, lazy routes)
- Angular Material 22 (Theming from Material 3 design tokens)
- NestJS 12 on Express 5 +
tsx(TypeScript server withcors); legacy routes are being ported from Express routers to Nest modules signature_padfor capturing signaturesexceljsfor.xlsxexport,jspdffor receipt/summary PDFsmarkdown-itfor Markdown rendering of titles and descriptions- Vitest + Playwright (3 browsers) for tests
- Docker (single-container deploy) optional
Requires Node ≥ 24.22.1 (or ≥ 26.12.1).
npm install
npm start # dev UI on http://localhost:4200 (proxies /api → :3000)
npm run start:api # Nest/Express API on http://localhost:3000
npm run start:all # both at onceOther scripts:
npm run lint # ESLint
npm run typecheck # tsc for app + specs
npm run typecheck:server # tsc for the API
npm run test:ci # unit tests (Vitest)
npm run e2e # Playwright end-to-end tests (chromium/firefox/webkit)
npm run build # production build into dist/
npm run check # full gate: lint + format:check + typecheck (+server) + tests + buildsrc/app/
core/
model/ # data model: forms, fields, values, validation
engine/ # evaluation: values, conditions, validations, calculations, templates
state/ # signal stores + FormsRepository (API client with offline fallback)
export/ # JSON schema, CSV/Excel/PDF exporters, mail channel, file helpers
auth/ # login service + route guard
i18n/ # en/de translations + reactive I18nService
landing/ # form list, share links, import, delete
builder/ # designer UI: canvas, palette, property panel, condition editor
runner/ # respondent view (incl. signature pad, PDF receipt, draft autosave)
results/ # submissions & CSV/Excel/PDF/mail export
login/ # password login screen
shared/model/ # element definitions, conditions, validation, values, submission
server/ # Nest + Express API: TypeORM datasource, entities, auth, user/form/submission endpoints (lives under src/)
e2e/ # Playwright specs + coverage collection
deploy/ # optional nginx reverse-proxy config
The simplest option is the included single-container Docker setup:
FORMMAKER_PASSWORD=secret docker compose up -d --buildThe container serves both the built Angular SPA (with SPA fallback) and the API on port 3000; persist the SQLite database through a mounted volume, or configure MySQL via the environment variables above. Alternatively, serve the static dist/ build with nginx and reverse-proxy /api/ to the Node backend — see deploy/nginx/form-maker.conf.
API requests are rate limited per client IP over 15 minutes: RATELIMIT (default 300) for /api, and RATELIMIT_AUTH (default 20) for login and registration. Behind a reverse proxy, set TRUST_PROXY (e.g. TRUST_PROXY=1 for one proxy hop) so the limits apply to the real client IP instead of the proxy's.
This project was built for the most part with AI assistance (opencode with a large language model), including significant portions of the code, the tests, and this README. It's a working, tested mini-go-live rather than a production product — expect rough edges.