nette/forms
Nette Forms is a PHP library for building, rendering, and validating HTML forms with minimal code. It offers CSRF protection, file uploads, flexible controls, and a rich set of validation rules through a clean, secure, and extensible API.
Architecture fit — conditional, not native. nette/forms is a standalone-capable component from the Nette ecosystem, not a Laravel package. Its object model (Nette\Forms\Form, Container, BaseControl, Rules, Validator) is a self-contained form tree with its own renderer, its own CSRF mechanism (CsrfProtection, backed by Nette\Http\Session + Request::isSameSite()), and its own client-side runtime (netteForms.js, rewritten to TypeScript in v3.2.4). Laravel's equivalents are Illuminate\Validation, Blade components, VerifyCsrfToken, and @error. Adopting this package means two parallel request-validation and CSRF stacks in one application unless the integration is deliberately scoped.
The fit is strong in three scenarios and weak in one:
addProtection(), and per-control error messaging reduce bespoke validation code.FormRequest, and $request->validate(). You would be fighting the framework's grain for rendering, error bags, and CSRF.Integration feasibility — medium effort, real seams. Standalone instantiation exists (Form::initialize(), added v3.1.2), so a Laravel controller can drive it. The concrete seams to bridge:
Nette\Http\IRequest and populate via loadHttpData(). In Laravel you either construct a Nette\Http\Request from Request::createFromGlobals() (works only for genuine globals, not for Laravel's normalized/overridden request) or feed values with setValues() and accept the loss of loadHttpData() semantics — including the newer SubmitButton/submitter handling in v3.2.5–v3.2.6.isSameSite() and CsrfProtection are token/session-based on Nette's own session abstraction. Laravel's XSRF-TOKEN/session token must be reconciled, or you disable one and risk a silent double-protection gap. allowCrossOrigin() (used in tests since v3.2.9) is the escape hatch, and it is a security-relevant one.DefaultFormRenderer (framework-neutral HTML, customizable via setOptionAttribute, ControlGroup, nextTo), or Latte templates via {form}, {input}, {label}, {inputError}. Latte 3.1 is supported (v3.2.7–3.2.8), but that means adding Latte alongside Blade. Budget for a Blade-shim adapter or commit to Latte for the form-rendering surface only.netteForms.js expects data-nette-rules attributes on elements with data-nette-rules processing (v3.2.3 narrowed this). It must be loaded outside Vite's default pipeline or wrapped as an entry point; it will not participate in Laravel's @vite HMR flow without configuration.Technical risk — moderate, mostly ecosystem and lifecycle risk rather than defect risk.
getValues(true) deprecated (v3.2.0), Form::getValues() returning only scoped controls (v3.1.0), addImage() → addImageButton(), getUnsafeValues() → getUntrustedValues(), {inputError} requiring an argument (v3.2.0). Minor versions have carried behavioral breaks. Pin tightly; do not float ^3.2 without a CI gate.NOASSERTION. This is a gating item, not a footnote. Legal must confirm the actual terms before it enters a commercial codebase — treat as a hard blocker for the spike exit criteria.Dependents: 0 in the supplied metadata is almost certainly a measurement artifact (the package is widely used in the Nette world), but it must be verified via Packagist reverse dependencies before anyone cites it as an ecosystem-health signal in a decision doc.nette/utils, nette/http, nette/component-model. These are small and stable but add an unversioned-by-Laravel dependency graph to the SBOM.Key questions to resolve before committing engineering time
FormRequest today, and what specifically is missing — conditional/toggle rules, client-server rule parity, file/mime validation, enum hydration?Stack fit. Treat nette/forms as a bounded component, not a framework. The viable shape is:
| Layer | Owner | Notes |
|---|---|---|
| HTTP request/response | Laravel | nette/http used only as an adapter input |
| Session | Laravel | Bridge to Nette\Http\Session or bypass CsrfProtection |
| CSRF | Laravel (single source) | Do not run two token systems |
| Form definition & server validation | nette/forms |
The actual value proposition |
| Client validation | netteForms.js |
Bundled as a Vite entry, fed by data-nette-rules |
| Rendering | DefaultFormRenderer → Blade partial |
Avoid Latte unless Nette templates already exist |
| Domain hydration | Container::getValues($dto) + enum conversion (v3.2.4) |
Replaces manual mapping |
Migration path.
DefaultFormRenderer, and Vite-bundled netteForms.js. Exit criteria: CSRF decision documented, license cleared, one form shipped behind a flag.NetteFormFactory contract + implementation that builds a Form, binds setValues() from the Laravel request, runs validate(), and returns errors in a shape Blade can render (map Nette errors → Laravel MessageBag). This is the only place the two worlds meet; keep it under ~200 lines and test it hard.Compatibility.
3.2.9 or later patch) in composer.json; add a Renovate/Dependabot rule requiring a human review on minor bumps given the BC history.psr/ and symfony/ majors — run composer why nette/utils and check for version skew with symfony/http-foundation.allowCrossOrigin() path is not enabled in production config (it is used in nette's own tests since 3.2.9 and is a quiet CSRF bypass).Sequencing (dependency-ordered).
netteForms.js (week 2).Maintenance. This is a maintenance-oriented upstream — v3.2.9 is explicitly "no API changes, type safety + static analysis" and the release cadence is steady (3.2.0 → 3.2.9 across roughly a year). The cost is not upstream churn; it is owning the adapter. Every Laravel upgrade touches the request/session bridge, and every nette/forms minor touches the value/error mapping. Budget roughly 0.5–1 day per quarter for version maintenance plus one adapter review per Laravel major.
Support. No vendor support. Recourse is GitHub issues, Nette's documentation, and the Nette community forum/Discord. Internal bus factor is the dominant risk: if one engineer understands the Container/Rules model and leaves, the adapter becomes a black box. Mitigate with an ADR, a written adapter contract, and a test suite that exercises validation, toggles, file upload, and CSRF explicitly.
Scaling. Negligible runtime footprint — forms are per-request object trees validated in-process; no I/O beyond session for CSRF. The netteForms.js bundle adds a small client payload; v3.2.3's formdata-based compact transmission and v3.2.4's TypeScript rewrite keep it modern but also mean older browser support assumptions changed. Validate against the support matrix. Under high concurrency the validation itself is not a bottleneck; file uploads via UploadControl follow PHP's upload_max_filesize/MAX_FILE_SIZE handling (auto-set since v3.0.3) and need the same ingress limits as any Laravel upload.
Failure modes and mitigations.
allowCrossOrigin() or from disabling CsrfProtection while assuming Laravel covers it on a route that bypasses VerifyCsrfToken. Mitigation: one CSRF owner, an explicit test asserting a missing-token POST is rejected.$errors/MessageBag. Mitigation: adapter contract test that fails if a control error is not surfaced in the Blade bag.netteForms.js accepts a value the server rejects, or vice versa. The rule export is the package's main value and its main hazard. Mitigation: golden-file test asserting the exported data-nette-rules JSON for each form.setValues() sees normalized, not raw, input). Mitigation: explicit adapter tests against Request::create() fixtures.Ramp-up. Expect ~3–5 days for a mid-level PHP engineer to become productive in the Container/Rules/Validator model, assuming documentation and one worked example. The steeper curve is the rendering model (DefaultFormRenderer parts, ControlGroup, nextTo, setOptionAttribute) if you are not using Latte — budget another 2–3 days for the Blade partial, and expect the first form to be 2–3× the effort of subsequent ones. If the team has no Nette background, assume the adapter is written by your strongest engineer and reviewed by a second; that is the only part a single person can quietly get wrong in a security-relevant way.
Bottom line for the roadmap. This is a conditional adoption. The validation and CSRF primitives are mature and well-maintained, but the value only materializes if form-heavy work is a genuine roadmap theme (intake, onboarding, compliance capture) and the team can absorb a permanently owned Laravel↔Nette adapter. If forms are incidental, Illuminate\Validation plus a small amount of custom rule code delivers most of the benefit with none of the dual-stack cost. Gate the decision on license clearance, one shipped spike form, and a measured per-form conversion cost from the pilot — not on the feature list.
How can I help you explore Laravel packages today?