OpnForm is an open-source form builder.
The easiest way to get started with OpnForm is to sign up for our managed service in the Cloud. You get support, backups, upgrades, and more. Your data is safe and secure, and you don't need to worry about maintenance or infrastructure. Check out our quick overview of cloud vs self-hosting.
The cloud pricing page shows monthly equivalents for yearly billing by default. The Monthly / Yearly selector updates both the plan cards and the feature comparison table; yearly plans are billed annually.
- 🚀 No-code builder with unlimited forms & submissions
- 📝 Various input types: Text, Date, URL, File uploads & much more
- 🌐 Embed anywhere
- 📧 Email notifications
- 💬 Integrations (Slack, Webhooks, Discord)
- 🧠 Form logic & customization
- 🛡️ Captcha protection
- 📊 Form analytics
For a complete list of features and detailed documentation, visit our Technical Documentation.
OpnForm's remote MCP server lets AI agents create and preview a private form draft without requiring a login. OAuth adds workspace-aware form management and read-only submission search, statistics, and exports. The installable portable and native plugin package lives in plugins/opnform/. See the MCP integration guide.
The easiest way to get started with OpnForm is through our official managed service in the Cloud.
For self-hosted installations, please refer to our Deployment Guides. For local development, we provide a minimal Docker-based setup - check out our Docker Development Guide.
The client npm run build also checks the public form bundle size and eager dependencies. This check reads .nuxt/dist/client/_nuxt, independently of Nitro's deployment output directory (including AWS Amplify's .amplify-hosting). Run its regression tests from client/ with node --test scripts/check-public-form-bundle.test.mjs.
The dashboard fetches lightweight summaries in batches of 50. The API keeps its default page size of 10 and its maximum of 100 for other callers. It paginates forms before loading view and completed-submission counts for the returned IDs. Historical views use an index on form_statistics.form_id, and the aggregate is explicitly aliased as total_views_count so the model uses the preloaded value.
The index migration runs outside a transaction and uses CREATE INDEX CONCURRENTLY on PostgreSQL to keep writes available. It also recovers an invalid index left by an interrupted build. Run this migration through Laravel normally; do not wrap it in an external transaction. MySQL and SQLite use the standard schema index migration.
Codex creates an isolated local environment for each worktree. It uses a dedicated PostgreSQL Docker volume and local Laravel/Nuxt processes, so sibling worktrees do not share ports, data, or API configuration.
Use the Codex Start app, Reset DB, and Stop app actions when available. You can also run the scripts directly:
./scripts/codex-worktree-setup.sh
./scripts/codex-worktree-up.shThe startup command prints the worktree-specific URL. Opening its root URL automatically signs in the local Codex admin (e2e@example.test / Abcd@1234). The seed also creates three forms, including five completed submissions across two public forms, so the dashboard and submission screens are immediately useful. Reset only the current worktree database with:
./scripts/codex-worktree-reset-db.shRun the browser suite against the running worktree with:
./scripts/codex-worktree-test-e2e.shThe API resolves dependencies for PHP 8.3.36; use a patched PHP 8.3 or 8.4 runtime compatible with the lock and run composer check-platform-reqs. The client requires Node ^22.22.3 || ^24.15.0 || >=26; CI and Docker use 24.22.1, also recorded in client/.nvmrc.
Nuxt Content uses the native node:sqlite connector, including on Amplify's Node 24 runtime. Its Zod dependency is pinned to the patched v4 line through a scoped npm override; keep that override until Nuxt Content updates its dependency range.
The executable npm audit policy is in scripts/npm-exceptions.json. Dependabot checks Composer, client npm packages, and GitHub Actions weekly, with up to three open version-update PRs per ecosystem. Compatible application updates are grouped; major application dependency updates remain separate except for the coordinated VueUse packages. GitHub Actions updates are grouped together. Security update handling is unchanged.
Clearing a date with a click, Enter, or Space keeps the calendar closed. Reopen it to choose another date.
Spreadsheet exports preserve numeric zero values, account identifiers with leading zeroes, and Unicode. Null and empty fields remain blank.
The PDF editor removes zones linked to deleted fields and lists them before you continue. Save the template to keep the cleanup. Mappings retain field names when created or saved, so deleted fields can still be identified. Older mappings without a saved name appear as “Deleted field” with their page number.
PDF output follows the template page order and dimensions. Zones near the bottom of a page stay on that page; text is clipped to the zone height rather than creating extra pages. The pagination cache version bypasses PDFs generated before this fix, without changing saved templates.
If you need help or have questions, please join our Discord community. For more information and assistance, check out the following resources:
OpnForm is open-source under the GNU Affero General Public License Version 3 (AGPLv3) or any later version. You can find it here.
OpnForm uses a dual-license model to make the project sustainable:
- Core OpnForm (AGPL-3.0): The main application is free and open-source under AGPLv3, giving you the freedom to use, modify, and distribute it.
- Enterprise Edition (Proprietary): Advanced features under
api/app/Enterprise/are available under our Enterprise License and Enterprise Terms. These features help fund ongoing development and keep OpnForm sustainable.
By offering Enterprise features alongside our open-source core, we can continue to invest in making OpnForm better for everyone while keeping the project financially sustainable.
Public submissions use the form's language for API validation messages. When adding a translated validation message, update the catalogs under api/resources/lang/ for all languages in Form::LANGUAGES, preserving placeholders such as :attribute, :min, and :max.
Run vendor/bin/pest --filter=FormValidationTranslationsTest from api/ to check supported catalogs and render invalid date range, rating, selection, and Arabic file-type errors. The tests do not require a database. The catalog check excludes custom attribute aliases and the legacy password key, whose structure differs between Laravel versions. Client text coverage is checked separately by test/unit/form-locale-keys.test.ts in client/.
