hadikhanzadeh / laravel-sanitizer
Recursive, filter-based input sanitization for Laravel applications, with per-field rules and dot-notation support.
Package info
github.com/hadikhanzadeh/laravel-sanitizer
pkg:composer/hadikhanzadeh/laravel-sanitizer
Requires
- php: ^8.4
- illuminate/contracts: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0
Suggests
- mews/purifier: Required if you use the built-in EditorFilter for rich-text sanitization.
README
Recursive, filter-based input sanitization for Laravel applications — with per-field rules, dot-notation support for nested data, a FormRequest trait for zero-boilerplate integration, and a sanitize middleware for route-level or global coverage.
Why this package
Cleaning request input in Laravel usually ends up as ad-hoc calls to trim() and strip_tags() scattered across controllers, or a single monolithic sanitizer class that's hard to extend. This package instead treats each sanitization step as a small, testable, swappable class — registered through config, resolved through the container, and safe for config:cache.
It's a spiritual successor to the now-unmaintained waavi/sanitizer, rebuilt for PHP 8.4 and Laravel 12/13 with an interface-based filter architecture.
Requirements
- PHP 8.4+
- Laravel 12.x or 13.x
Installation
composer require hadikhanzadeh/laravel-sanitizer
The service provider is auto-discovered — no manual registration needed.
Optionally publish the config file to customize registered filters or defaults:
php artisan vendor:publish --tag=sanitizer-config
Basic usage
Resolve the Sanitizer service directly:
use HadiKhanzadeh\LaravelSanitizer\Sanitizer; $sanitizer = app(Sanitizer::class); $clean = $sanitizer->clean([ 'name' => ' <b>Hadi</b> ', 'bio' => '<script>alert(1)</script><p>Hello</p>', ]); // ['name' => 'Hadi', 'bio' => 'Hello']
Or use the facade:
use HadiKhanzadeh\LaravelSanitizer\Facades\Sanitizer; $clean = Sanitizer::clean($request->all());
Automatic sanitization in FormRequests
Add the SanitizesInput trait to any FormRequest to sanitize its payload automatically before validation runs:
use HadiKhanzadeh\LaravelSanitizer\Concerns\SanitizesInput; use Illuminate\Foundation\Http\FormRequest; final class StoreProductRequest extends FormRequest { use SanitizesInput; public function rules(): array { return [ 'name' => ['required', 'string'], ]; } }
Per-field rules
Override sanitizationRules() to control which filters run on specific fields:
protected function sanitizationRules(): array { return [ 'description' => ['editor'], 'title' => ['trim', 'strip_tags'], ]; }
Nested fields (dot notation)
protected function sanitizationRules(): array { return [ 'address.postal_code' => ['trim'], ]; }
Changing the default pipeline
Fields without an explicit rule fall back to config('sanitizer.default_filters') (trim, strip_tags by default). Override per request:
protected function defaultSanitizationFilters(): ?array { return ['trim']; }
Opting out
protected bool $sanitizeInput = false;
Before/after hooks
protected function beforeSanitization(): void { // runs before the Sanitizer touches the payload } protected function afterSanitization(): void { // runs after sanitization, before validation }
Global or per-route sanitization via middleware
If you'd rather sanitize input at the HTTP layer instead of (or in addition to) individual FormRequests, the package ships a sanitize middleware alias. Unlike the SanitizesInput trait, the middleware always applies the configured default filter pipeline (config('sanitizer.default_filters')) uniformly to every field — it has no concept of per-field rules, since a route (not a single request class) is where it's applied.
Apply to specific routes or groups
use Illuminate\Support\Facades\Route; Route::middleware('sanitize')->group(function () { Route::post('/products', [ProductController::class, 'store']); Route::put('/products/{product}', [ProductController::class, 'update']); });
Or on a single route:
Route::post('/comments', [CommentController::class, 'store'])->middleware('sanitize');
Apply globally to every request
To sanitize all incoming input application-wide, register the alias as a global middleware. In Laravel 11+ (bootstrap/app.php):
use HadiKhanzadeh\LaravelSanitizer\Http\Middleware\SanitizeInput; ->withMiddleware(function (Middleware $middleware) { $middleware->append(SanitizeInput::class); })
Note: The package intentionally does not register this middleware globally by itself — only the
sanitizealias is registered. Applying sanitization to every request in every application by default would be a surprising, hard-to-override side effect of merely installing the package. Opt in explicitly at the route, group, or global level as shown above.
Middleware vs. the SanitizesInput trait
Middleware (sanitize) |
SanitizesInput trait |
|
|---|---|---|
| Scope | Route / route group / global | Per FormRequest class |
| Per-field rules | Not supported — default pipeline only | Full support via sanitizationRules() |
| Dot-notation nested rules | Not supported | Supported |
| Opt-out | Simply don't apply the middleware | protected bool $sanitizeInput = false; |
Use the middleware for blanket, low-effort coverage across many simple routes; use the trait when a specific request needs per-field control (e.g. an editor field that must allow limited HTML).
Built-in filters
| Name | Class | Description |
|---|---|---|
trim |
TrimFilter |
Trims leading/trailing whitespace. |
strip_tags |
StripTagsFilter |
Removes all HTML/PHP tags. |
stripslashes |
StripSlashesFilter |
Removes backslashes. Registered but not in the default pipeline — a legacy filter, opt in per-field only if needed. |
editor |
EditorFilter |
Sanitizes rich-text HTML via mews/purifier, stripping dangerous attributes (onclick, javascript: URLs) that a plain tag allow-list would miss. Requires composer require mews/purifier and a purifier.editor config preset. |
Note:
htmlspecialcharsis intentionally not included. Escaping is an output-layer concern (Blade, API resources) — encoding on input causes double-encoding when the value is escaped again later.
Adding a custom filter
Implement SanitizationFilter:
namespace App\Sanitization\Filters; use HadiKhanzadeh\LaravelSanitizer\Contracts\SanitizationFilter; final readonly class LowercaseFilter implements SanitizationFilter { public function apply(mixed $value): mixed { return is_string($value) ? mb_strtolower($value) : $value; } }
Register it in config/sanitizer.php:
'filters' => [ // ... 'lowercase' => \App\Sanitization\Filters\LowercaseFilter::class, ],
Use it like any other filter name in your rules.
Testing
composer test
License
MIT. See LICENSE.md.