hadikhanzadeh/laravel-sanitizer

Recursive, filter-based input sanitization for Laravel applications, with per-field rules and dot-notation support.

Maintainers

Package info

github.com/hadikhanzadeh/laravel-sanitizer

pkg:composer/hadikhanzadeh/laravel-sanitizer

Transparency log

Statistics

Installs: 8

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.4 2026-08-28 13:51 UTC

This package is auto-updated.

Last update: 2026-08-28 13:51:51 UTC


README

Latest Version on Packagist Tests Total Downloads License

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 sanitize alias 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: htmlspecialchars is 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.