[ Laravel Http Request ]

How to Validate & Sanitize Query Parameters in Laravel (2026)

Unvalidated query parameters are a silent source of bugs and security holes. Here's how to validate and sanitize them properly in Laravel — with clean, copy-paste examples for filters and search.

··4 min read·695 words
How to Validate & Sanitize Query Parameters in Laravel (2026)

Query parameters feel harmless — they're just filters and search terms in the URL, right? But anything a user can type into a URL is untrusted input. An unvalidated ?sort= or ?page= can break a database query, expose data you didn't intend, or crash a page with a bad value. The good news: Laravel makes validating and sanitizing them straightforward.

This guide shows how to validate query parameters, apply safe defaults, and whitelist allowed values — so the data flowing into your controllers is always clean.

Why query parameters need validation

It's easy to trust query strings because you wrote the links that use them. But a user (or a bot) can change the URL to anything:

  • ?page=-5 or ?page=abc — breaks pagination logic

  • ?sort=password — tries to sort by a column you never meant to expose

  • ?perPage=100000 — a denial-of-service via a huge result set

  • ?search=<script> — untrusted text that ends up rendered on the page

Every one of these is prevented by validating the input before you use it.

The simplest approach: $request->validate()

For a quick controller-level check, validate() runs your rules and, on failure, automatically redirects back (web) or returns a 422 JSON response (API):

php

public function index(Request $request)
{
    $validated = $request->validate([
        'search'  => 'nullable|string|max:100',
        'page'    => 'nullable|integer|min:1',
        'perPage' => 'nullable|integer|min:1|max:100',
        'sort'    => 'nullable|in:name,price,created_at',
    ]);

    // $validated only contains the keys you defined above
    $search = $validated['search'] ?? null;
}

Two things are doing a lot of work here. max:100 and min:1 keep values in a sane range, and in:name,price,created_at whitelists the only sort columns you allow — so ?sort=password is rejected outright.

Always apply safe defaults

Validation rejects bad values, but you still want sensible fallbacks when a parameter is simply missing. Combine validation with defaults using query()'s second argument:

php

$page    = (int) $request->query('page', 1);
$perPage = (int) $request->query('perPage', 15);
$sort    = $request->query('sort', 'created_at');

Casting to (int) is a small but important sanitizing step — it guarantees you're working with a number before it reaches a database query or a LIMIT clause.

Whitelisting: the most important habit

Never pass a raw parameter straight into a query as a column name, direction, or table. Always check it against a list of values you explicitly allow:

php

$allowedSorts = ['name', 'price', 'created_at'];
$sort = $request->query('sort', 'created_at');

if (! in_array($sort, $allowedSorts, true)) {
    $sort = 'created_at';
}

$direction = $request->query('direction') === 'asc' ? 'asc' : 'desc';

$products = Product::orderBy($sort, $direction)->paginate($perPage);

This single pattern closes off an entire class of bugs and injection risks. If it's not on the list, it doesn't get used — full stop.

Cleaner still: a Form Request

When the same validation rules show up in more than one place, move them into a Form Request class. It keeps controllers thin and rules reusable:

php

php artisan make:request ProductFilterRequest

php

public function rules(): array
{
    return [
        'search'  => 'nullable|string|max:100',
        'page'    => 'nullable|integer|min:1',
        'perPage' => 'nullable|integer|min:1|max:100',
        'sort'    => 'nullable|in:name,price,created_at',
    ];
}

Then type-hint it in the controller and you get validated data for free:

php

public function index(ProductFilterRequest $request)
{
    $validated = $request->validated();
    // ...
}

A note on sanitizing for output

Validation handles what comes in. When you display a query value back on the page — like showing "Results for: {search}" — let Blade escape it for you. The {{ }} syntax automatically escapes output, which neutralizes ?search=<script> attempts:

blade

<p>Results for: {{ request('search') }}</p>

Avoid {!! !!} for user input — that's unescaped output and re-opens the door you just closed.

Wrapping up

Query parameters are user input, so treat them like it: validate types and ranges, whitelist any value that becomes a column or direction, apply safe defaults for what's missing, and let Blade escape anything you echo back. A few lines of validate() and an in_array check are all it takes to make your filters both bug-free and secure.

This builds directly on how you read request data — if you need a refresher, see $request->query() vs $request->input() in Laravel and handling GET, POST, PUT & PATCH requests in Laravel routes. Next up, I'll show how to build a fully filterable API endpoint from these query strings, start to finish.