Rules To Schema
#Rules to Schema
Build JSON Schema from Laravel validation rules.
You already describe the shape of your data once, in validation rules. This turns that description into a JSON Schema, so you don't write it a second time to define an LLM tool, a structured output format, or an OpenAPI body.
composer require waleedhu/rules-to-schema
Requires PHP 8.3+ and Laravel 13's illuminate/json-schema.
#Usage
use WaleedHu\RulesToSchema\RuleSchema; $schema = RuleSchema::fromRules([ 'title' => 'required|string|min:3|max:120', 'status' => 'required|in:draft,published,archived', 'author.name' => 'required|string|max:50', 'author.email' => 'required|email', 'rating' => 'nullable|numeric|between:0,5|multiple_of:0.5', 'tags' => 'array|max:5', 'tags.*' => 'string|max:20', ]);
$schema is an Illuminate\JsonSchema\Types\ObjectType, so toArray() gives you a PHP array and casting to string gives you JSON:
{ "properties": { "title": { "minLength": 3, "maxLength": 120, "type": "string" }, "status": { "enum": ["draft", "published", "archived"], "type": "string" }, "author": { "properties": { "name": { "maxLength": 50, "type": "string" }, "email": { "format": "email", "type": "string" } }, "type": "object", "required": ["name", "email"] }, "rating": { "minimum": 0, "maximum": 5, "multipleOf": 0.5, "type": ["number", "null"] }, "tags": { "maxItems": 5, "items": { "maxLength": 20, "type": "string" }, "type": "array" } }, "type": "object", "required": ["title", "status"] }
Note that author itself is optional here, because no rule marks it required — but name and email are required within it if it is present. Add 'author' => 'required' to require the object too.
#Reusing a form request
Rules live in a form request more often than in an array, and they work just as well:
RuleSchema::fromRules((new StorePostRequest)->rules());
#Nesting and arrays
Dot notation becomes nested objects, and * becomes array items:
RuleSchema::fromRules([ 'attachments' => 'array', 'attachments.*.url' => 'required|url', 'attachments.*.size' => 'integer|min:1', ]);
{ "properties": { "attachments": { "items": { "properties": { "url": { "format": "uri", "type": "string" }, "size": { "minimum": 1, "type": "integer" } }, "type": "object", "required": ["url"] }, "type": "array" } }, "type": "object" }
#What gets mapped
Only rules with an exact JSON Schema counterpart.
| Rules | Becomes |
|---|---|
string, integer, numeric, boolean, array, list, decimal |
type |
required |
the parent's required list |
nullable |
"null" added to type |
min, max, size, between |
minLength/maxLength, minimum/maximum, or minItems/maxItems |
multiple_of |
multipleOf |
in |
enum |
regex |
pattern |
email, url, uuid, ipv4, ipv6, date |
format |
Bounds are resolved in the unit the type is measured in, the same way the validator sizes a value: min:3 is minLength on a string, minimum on a number, and minItems on an array. A field with no type rule is treated as a string, which is also how the validator sizes an untyped value.
#What gets ignored
Anything without an exact counterpart is skipped rather than approximated, so the schema never claims a constraint the rules don't express:
- Rules with no equivalent, such as
confirmed,existsandmimes. ip, which accepts either version, so it matches neither theipv4nor theipv6format.ulid, because a ULID is not a UUID.- Rule objects, closures and invokable rules, whose logic isn't inspectable.
#Testing
composer test
#License
MIT.