Add one action attribute to any HTML form and start collecting submissions.
No coding. No SaaS subscription. No vendor lock-in.
Quick Start Β· Features Β· Configuration Β· Self-host
FormTo is an open-source, self-hosted alternative to Formspree, Formcarry and Basin. Point your HTML form at FormTo, and every submission is saved, searchable, exportable, and delivered to your inbox (or Telegram, or Slack, or any webhook).
<form action="https://forms.example.com/f/contact-abc123" method="POST">
<input name="name" required />
<input name="email" type="email" required />
<textarea name="message"></textarea>
<button type="submit">Send</button>
</form>That's it. No JavaScript. No API keys. Submissions appear in your dashboard instantly.
| π₯ Submissions inbox | Read, archive, reply by email, add internal notes, change status (new / in-progress / resolved) |
| π§ Email notifications | Bring your own SMTP β Gmail, Mailgun, Postmark, Resend, anything |
| π¬ Telegram & Slack | Native integrations via bot token or incoming webhook |
| π Webhooks | POST every submission as JSON to Zapier, Make, n8n, Discord, or your own endpoint |
| π¨ Custom email templates | Brand your notifications with HTML and your logo |
| π Analytics | Submissions over time, per-form breakdown, top fields |
| π Hosted form pages | Share a standalone form at /f/:endpoint β no HTML required |
| π‘οΈ Spam protection | Built-in honeypot, rate limiting, and block-by email/domain/IP |
| π Auto-close | Stop accepting submissions after N responses or on a specific date |
| π·οΈ Tags & filters | Organize forms with labels and search across everything |
| π¦ CSV + JSON export | Own your data β download it anytime |
| π§ First-run wizard | Zero config. Open the app, create your account, done |
Requires Docker and Docker Compose. That's the only dependency.
# 1. Clone
git clone https://github.com/lumizone/formto
cd formto
# 2. Configure
cp formto.env.example formto.env
# Edit formto.env β set DOMAIN, POSTGRES_PASSWORD, JWT_SECRET
# 3. Launch
docker compose up -dOpen https://your-domain.com β the first-run setup wizard will greet you β create your account β create your first form.
HTTPS is automatic. Caddy obtains and renews Let's Encrypt certificates for your domain. No certbot, no cron jobs, no
--renewflags.
Everything lives in a single formto.env file.
DOMAIN=forms.example.com
POSTGRES_PASSWORD=<openssl rand -base64 32>
JWT_SECRET=<openssl rand -hex 32>SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=you@gmail.com
SMTP_PASS=your-app-password
FROM_EMAIL=FormTo <noreply@forms.example.com>SMTP, Telegram and Slack credentials can also be configured per account in the UI (Account β Notifications), so you don't have to bake them into env vars.
On a VPS with IP only? Edit Caddyfile, replace {$DOMAIN} with :80, leave DOMAIN= empty. Access the app at http://your-server-ip.
Internet
β
βΌ
βββββββββββββββ
β Caddy β β auto HTTPS (Let's Encrypt)
ββββββββ¬βββββββ
β
ββββββββββββΌβββββββββββ
βΌ βΌ βΌ
ββββββββββ ββββββββββ ββββββββββββ
βFrontendβ βBackend β β /f/* β
β React β βFastify β β (public β
β :80 β β :3001 β β forms) β
ββββββββββ βββββ¬βββββ ββββββββββββ
β
βΌ
ββββββββββββ
βPostgreSQLβ
β :16 β
ββββββββββββ
docker compose up -d # start
docker compose down # stop
docker compose logs -f backend # tail logs
docker compose up -d --build # rebuild after pulling updates# Backup
docker compose exec postgres pg_dump -U formto formto > backup_$(date +%F).sql
# Restore
cat backup.sql | docker compose exec -T postgres psql -U formto formtogit pull
docker compose up -d --buildSchema migrations run automatically on boot β no manual steps.
| Layer | Technology |
|---|---|
| Frontend | React 19, Vite, Tailwind CSS v4, Radix UI |
| Backend | Fastify, Node.js 20, postgres.js, jose (JWT), nodemailer |
| Database | PostgreSQL 16 |
| Reverse proxy | Caddy 2 (automatic HTTPS) |
| Deployment | Docker Compose |
FormTo ships with sensible defaults out of the box:
- π Passwords hashed with bcrypt
- π« JWT auth (HS256, 7-day TTL) with startup warnings on default secrets
- πΈοΈ SSRF protection on webhook URLs (DNS pinning, private-IP blocking)
- π§± Rate limiting on all public endpoints
- π― Honeypot and spam detection on every form
- π« SQL injectionβproof β 100% parameterized queries (postgres.js tagged templates)
- π‘οΈ XSS-safe email templates and hosted forms
Found a vulnerability? Please open a private security advisory on GitHub.
Pull requests are welcome! For major changes, please open an issue first to discuss what you'd like to change.
- Fork the repo
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes
- Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
AGPL-3.0 β free to use, modify, and self-host. If you distribute a modified version or run it as a public service, you must publish your changes under the same license.
Built with β€οΈ for developers who value ownership of their data.
β Star this repo if you find it useful!