Dynamic Form Validation in Laravel: Safely Storing Rules in the Database | FilaForms                                 [ ![Filaforms Logo](https://filaforms.app/logo.svg)FilaForms

 ](https://filaforms.app)  [ Features ](https://filaforms.app#features) [ Pricing ](https://filaforms.app/pricing) [ Blog ](https://filaforms.app/blog) [ Documentation ](https://docs.filaforms.app)  [ Try Demo ](https://filaforms.app/login) [ Get Started ](https://filaforms.app/pricing#plans) 

 [ Features ](https://filaforms.app#features) [ Pricing ](https://filaforms.app/pricing) [ Blog ](https://filaforms.app/blog) [ Documentation ](https://docs.filaforms.app) [ Try Demo ](https://filaforms.app/login) [ Get Started ](https://filaforms.app/pricing#plans) 

   ![FilaForms](https://filaforms.app/logo.svg) FilaForms 

 TutorialsDynamic Form Validation in Laravel: Safely Storing Rules in the Database
========================================================================

 filaforms.app/blog

  [    Back to blog ](https://filaforms.app/blog) [ Tutorials ](https://filaforms.app/blog/category/tutorials) 

Dynamic Form Validation in Laravel: Safely Storing Rules in the Database
========================================================================

 Manuk Minasyan ·  August 18, 2026  · 5 min read 

 A database-driven rule is executable configuration. The risky implementation is to let a form author store any Laravel rule string and pass it directly to `Validator::make()`. The production implementation is a small, typed language that your application owns.

The goal is not to reproduce every Laravel validation feature in JSON. It is to support the rules form authors actually need while keeping table names, PHP classes, regular expressions, tenant scopes, and authorization decisions on the server.

Store identifiers, not executable strings
-----------------------------------------

A useful rule document is deliberately boring:

```json
{
  "name": "max_length",
  "parameters": { "value": 200 }
}

```

The builder may choose `max_length` and a bounded integer. The application registry maps that identifier to Laravel's `string|max:200`. It should reject unknown identifiers, missing parameters, values outside the allowed range, and unexpected keys when the schema is saved.

Do not store closures, PHP class names, SQL fragments, connection names, arbitrary tables/columns, or free-form Laravel rule strings. If a rule needs application behavior, register a server-owned compiler for it.

Validate the rule schema twice—but for different reasons
--------------------------------------------------------

There are two validation boundaries:

1. **Builder save:** Is this a valid rule definition? Is `max_length.value` an integer between your bounds? Is this rule allowed for this field type?
2. **Public submit:** Does the visitor's value satisfy the compiled rules for the published form revision?

Builder validation protects the configuration language. Submission validation protects application data. Skipping either produces failures that the other cannot catch.

Build an allowlisted compiler
-----------------------------

A registry can return strings for simple rules and `Rule` objects or custom rule objects for anything touching the database:

```php
$compilers = [
    'required' => fn (array $parameters): array => ['required'],
    'email' => fn (array $parameters): array => ['email:rfc'],
    'max_length' => fn (array $parameters): array => [
        'string',
        'max:'.$parameters['value'],
    ],
    'active_product' => fn (array $parameters): array => [
        Rule::exists(Product::class, 'id')
            ->where(fn (Builder $query) => $query
                ->where('tenant_id', tenant()->id)
                ->where('is_active', true)),
    ],
];

```

The exact API is less important than ownership: the stored schema selects a reviewed capability; it never decides which model or tenant condition is queried.

Laravel warns against passing user-controlled values to `Rule::unique()->ignore()`. Use a trusted model key, not a request value, when editing an existing record. Review the [Laravel validation documentation](https://laravel.com/docs/13.x/validation) for the current rule behavior.

Nested fields need an explicit data shape
-----------------------------------------

Repeaters and grouped fields require more than joining labels with dots. Define the expected keys and compile paths such as:

```php
'attendees' => ['required', 'array', 'max:10'],
'attendees.*' => ['array:name,email'],
'attendees.*.name' => ['required', 'string', 'max:120'],
'attendees.*.email' => ['required', 'email'],

```

Naming allowed array keys matters. Otherwise values can survive inside a validated parent array even when no individual rule described them. Use `Rule::forEach` when each item needs a rule derived from trusted context.

Test reordered rows, deleted rows, sparse indexes, extra keys, and deeply nested payloads. Put a maximum on collection size before doing expensive per-item work.

Visibility is not validity
--------------------------

A browser can submit a hidden field. Conditional UI and server validation therefore need a shared policy.

If `company_name` is only relevant when `account_type=business`, decide what the server should do when it arrives for a personal account: exclude it, prohibit it, or ignore it. Do not merely remove the `required` rule.

Laravel's conditional, exclusion, and prohibition rules can express that policy. The condition must use trusted schema plus submitted values, not a client claim that the field was visible. Our [conditional-logic guide](/blog/conditional-logic-in-laravel-forms-when-fields-should-show-and-hide) covers the UI side.

Scope database rules explicitly
-------------------------------

An option visible in a select is not automatically authorized. A visitor can change its ID before submission.

Every `exists` or `unique` rule needs the same ownership, tenant, active-state, and soft-delete semantics as the option query. Filament's tenant-aware helpers can scope rules in the panel, but a public route lives outside that context unless you restore it deliberately. The [Filament tenancy guide](https://filamentphp.com/docs/5.x/users/tenancy) calls this security-sensitive for a reason.

Keep cache keys scoped by tenant, locale, dependencies, and permission context. A globally cached list of “active products” can become a cross-tenant disclosure.

Bound expensive rules
---------------------

Put limits on field count, nesting, strings, list sizes, decimal precision, uploads, and any pattern matching. Free-form regular expressions create a regular-expression denial-of-service surface. Prefer named, reviewed patterns; reject regex input from ordinary form authors.

The [OWASP input validation guidance](https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html) recommends allowlisting and server-side enforcement because browser checks are bypassable.

Tests worth publishing
----------------------

A compiler test suite should prove more than the happy path:

- unknown rules and parameters fail at builder save;
- `max_length` refuses negative or excessive bounds;
- unknown request keys do not appear in validated data;
- repeater items cannot escape their allowed shape;
- a forged option ID from another tenant fails;
- hidden fields follow the exclusion/prohibition policy;
- a hostile regex definition is impossible to store;
- changing the current form does not change rules for an in-progress published revision.

Uploads deserve their own controls; use the [file-upload security checklist](/blog/file-uploads-in-filament-forms-storage-validation-and-security) rather than treating MIME validation as proof a file is safe.

The safe mental model is a compiler: authors select capabilities from a constrained language, the server validates that document, and only trusted code turns it into Laravel rules. That design stays extensible without turning a JSON column into remote code or arbitrary database access.

 Related posts
-------------

 [  Tutorials   Jul 28, 2026  

 Form Performance in Laravel: Loading, Validation, and Submission Speed 
------------------------------------------------------------------------

Form page loads in 2.8 seconds. INP is 380 ms. Submit lags. Conversion drops and you feel it but can't see why. Here's the five-number checklist that finds the leak.

 ](https://filaforms.app/blog/form-performance-laravel-loading-validation-submission-speed) [  Tutorials   Jul 14, 2026  

 Signature Fields in Laravel Forms: When You Need a Real Signature, Not a Checkbox 
-----------------------------------------------------------------------------------

Checkbox-as-signature doesn't fly for NDAs, waivers, or parental consent. DocuSign is $40/user. Here's how the signature field in FilaForms works — and when it's enough.

 ](https://filaforms.app/blog/signature-fields-in-laravel-forms) [  Tutorials   Jun 30, 2026  

 Using AI to Classify Form Submissions in Laravel 
--------------------------------------------------

Your support form gets 100 messages a week. Two are urgent. The 98 "how do I reset my password" messages bury them. Here's a Laravel listener that classifies submissions in under a second — and what it costs.

 ](https://filaforms.app/blog/using-ai-to-classify-form-submissions-in-laravel) 

    ![FilaForms Logo](/logo.svg) FilaForms 

 Laravel form infrastructure for Filament. Stop rebuilding forms on every project.

 ### Product

 [ Features ](https://filaforms.app#features) [ Documentation ](https://docs.filaforms.app) [ Blog ](https://filaforms.app/blog) [ Pricing ](https://filaforms.app/pricing) [ About ](https://filaforms.app/about) [ Contact ](mailto:hello@filaforms.app) 

 ### Legal

 [ Terms of Service ](https://filaforms.app/terms-of-service) [ Privacy Policy ](https://filaforms.app/privacy-policy) 

  © 2025-2026 FilaForms. All rights reserved.

 [    ](mailto:hello@filaforms.app) [    ](https://x.com/MinasyanManuk)
