wt-health/laravel-json-schema-request
A Laravel package for validating incoming requests against JSON Schema definitions. Define schemas once, keep controllers clean, and get precise, standards-based validation errors for your API or application requests.
v4.0.0 is a compatibility release with two breaking changes, so before writing any code confirm your app version:
v3.2.x and pin it in composer.json ("wt-health/laravel-json-schema-request": "^3.2") until you upgrade the framework.Minimal adoption steps:
composer require wt-health/laravel-json-schema-request:^4.0 — check the resolution with composer update --dry-run first, since dropping Laravel 11 changes the constraint graph.vendor/wt-health/laravel-json-schema-request/src/ — the base request class and the service provider are the whole package surface. There is no elaborate config to learn.Validator::make() rules, and port it. Create a request class extending the package's base request, implement the schema method (returning the JSON Schema as a PHP array), and type-hint it on the controller method — Laravel resolves and validates it exactly like a FormRequest.That single endpoint migration tells you everything: whether your schema dialect is supported, how errors are shaped, and whether your authorize logic behaves as expected under the new 403 semantics.
Schema-per-endpoint request classes. Keep one request class per route, mirroring FormRequest conventions. The schema lives in a method returning a plain array — keep it declarative and free of $this calls so it can be extracted, cached, or snapshot-tested.
Extract shared fragments into files, not PHP duplication. Define reusable objects (addresses, money, pagination envelopes) once under resources/schemas/ and compose them with $ref. Resolving refs from disk keeps schemas readable and lets non-PHP tooling (editors, API contract tests, front-end validators) consume the same files. If the package resolves refs relative to a base URI you configure, set that base explicitly rather than relying on cwd.
Contract-first for write endpoints. JSON Schema's strength over Laravel's rule array is expressing shape: nested objects, arrays of objects, required at every level, additionalProperties. Use it for POST/PUT bodies where clients send real documents. Keep plain FormRequest for flat forms — you'll write more lines for less benefit.
Layered validation. Let the schema own shape (types, required keys, enums, nesting) and let a follow-up step own domain rules (uniqueness, existence, ownership). Schema validation that reaches into the database couples your contract to your data and produces errors that read like API errors rather than business errors.
Authorization as a guard, not a validator. Use the authorize hook for tenancy and ownership checks. With v4 returning 403, the semantics are crisp: "authenticated but not permitted." Keep authentication (401) in middleware and authorization here, and your status codes stay honest.
Version your schemas alongside migrations. When a payload shape changes, add a v2 schema file and route between them by API version header, rather than mutating the existing one. Cheap to do, and it prevents the "old mobile clients break" class of incident.
Test the contract, not the validator. Feature tests that postJson() a valid document and assert assertValid()/assertJsonValidationErrors() cover the behaviour. Add one test per schema that asserts an intentionally malformed payload fails — this catches accidental loosening (a stray additionalProperties: true, a forgotten required).
laravel/framework ^11 will make the solver fail in confusing ways.exclusiveMinimum as a boolean (draft-04) versus a number (draft-06+), $id versus id, and definitions versus $defs are not interchangeable. Pin the draft explicitly in your schema root and confirm which meta-schema the package validates against; a schema that "passes" in a linter may be interpreted differently at runtime."additionalProperties": false rejects them. If you expect Laravel-style "unknown field = error," you must opt in — and then remember that adding a field client-side becomes a breaking change.integer is not number. 1.0 fails integer in strict drafts, and PHP's JSON decoding will happily hand you a float. Normalize or widen the type rather than debugging a phantom 422.format is annotation-only by default. email, date-time, uuid may not be asserted unless the validator is configured to check formats. Don't assume a format: email is enforced — add a test that proves it./user/addresses/0/postcode), not a dot-keyed message bag. If your front end consumes errors.field, add a mapping layer in the request class or a response macro; otherwise every consumer needs rewriting.$ref path can produce a confusing schema-invalid error rather than a clean 500. When a request suddenly 422s on a schema you didn't touch, validate the schema document itself first.POST with no body or a non-JSON content type may bypass or hard-fail depending on middleware ordering. Test both, and decide deliberately whether an empty object is valid.messages()-equivalent for human-readable errors, schema loading from files, or a trait for tenant-scoped $ref roots. Subclass once per project rather than editing vendor code, so the next major is a one-line constraint bump.How can I help you explore Laravel packages today?