JSON form schema to validation rules
Turn a stored list of field definitions into Laravel validation rules and attribute names, so user-built forms are validated server side.
LaravelFormsvalidationform-builderjsonrules
Code
<?php
namespace App\Forms;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
/**
* Field shape: {"key":"email","label":"Work email","type":"email","required":true,
* "options":["a","b"],"min":1,"max":5}
*/
final class SchemaRules
{
/** @param list<array<string,mixed>> $fields */
public static function rules(array $fields): array
{
$rules = [];
foreach ($fields as $f) {
$key = 'answers.'.$f['key'];
$base = ($f['required'] ?? false) ? ['required'] : ['nullable'];
$type = match ($f['type']) {
'text' => ['string', 'max:'.($f['max'] ?? 255)],
'textarea' => ['string', 'max:'.($f['max'] ?? 5000)],
'email' => ['email:rfc', 'max:255'],
'url' => ['url', 'max:2048'],
'number' => ['numeric', ...self::bounds($f)],
'date' => ['date'],
'checkbox' => ['boolean'],
'select', 'radio' => ['string', Rule::in($f['options'] ?? [])],
'multiselect' => ['array', ...self::bounds($f)],
default => ['prohibited'], // unknown types can never be submitted
};
$rules[$key] = [...$base, ...$type];
if ($f['type'] === 'multiselect') {
$rules[$key.'.*'] = ['string', Rule::in($f['options'] ?? [])];
}
}
return $rules;
}
public static function attributes(array $fields): array
{
return collect($fields)->mapWithKeys(fn ($f) => ['answers.'.$f['key'] => $f['label'] ?? $f['key']])->all();
}
/** Validate a submission and return only the answers the schema knows about. */
public static function validate(array $fields, array $input): array
{
$v = Validator::make($input, self::rules($fields), [], self::attributes($fields));
return $v->validate()['answers'] ?? [];
}
private static function bounds(array $f): array
{
return array_filter([
isset($f['min']) ? 'min:'.$f['min'] : null,
isset($f['max']) ? 'max:'.$f['max'] : null,
]);
}
}
How it works
What it solves
When customers build their own forms, the fields live in a JSON column rather than in a FormRequest. You still need real server-side validation for every submission. This class translates the stored field list into the same rule arrays you would write by hand.
How it works
Each field has a key, a label, a type, an optional required flag and optional options, min and max. rules() maps every field to dotted rules under answers.<key>. A match expression picks the type rules: strings get a length cap, emails use RFC checking, selects and radios are limited with Rule::in() to the configured options, and multiselects validate the array plus every item with a .* rule.
Anything with an unknown type gets prohibited, so a tampered schema or payload cannot smuggle extra values. attributes() maps keys to labels so error messages read "Work email is required" instead of "answers.email". validate() runs the validator and returns only the validated answers, which drops unexpected keys.
Adapting it
- Add types such as
phoneorratingas new arms of thematch. - Add conditional logic by wrapping a field's rules with
required_if. - Cache the compiled rules per form version if forms are large.
Pitfalls
- Never trust field keys from the browser; they must come from the saved schema.
- Reject keys containing dots when saving the schema, because dots change meaning in rule names.
How to use it
- Store your fields as a JSON array on the form model (cast to
array). - Put
SchemaRules.phpinapp/Forms/. - In the public submit controller:
$answers = SchemaRules::validate($form->fields, $request->all()); - Save
$answersas JSON next to the response record. - Validation errors are thrown as a normal
ValidationException, keyed byanswers.<key>.
Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.