exists

Fails unless the value already exists in the given database table column.

Signature
exists:table,column

Parameters

ParameterTypeDescription
$tablestringTable name, or a fully qualified Eloquent model class whose table is used. May be prefixed with a connection name, e.g. `connection.staff`.
$columnoptionalstring= the field nameColumn to search. Defaults to the name of the field under validation.

Errors

Illuminate\Database\QueryException if the table or column does not exist, or if the named connection is not configured.

The field under validation must exist in a given database table. If the column is not specified, the field name is used — so 'state' => 'exists:states' looks for a states row whose state column matches.

Scope it, or it leaks

An unscoped exists:documents,id answers "does row 4711 exist?" for any caller. Walk the integers and you have enumerated the table, and the distinct error message tells an attacker which IDs are real. Constrain the query to what the requester may see:

Rule::exists('documents', 'id')->where(fn (Builder $q) => $q->where('team_id', $teamId))

This is not a substitute for an authorization check on the record you then load, but it stops validation itself from becoming an oracle.

Validating a whole array in one query

Assign both array and Rule::exists() to the same field and Laravel builds a single query to confirm every value exists, rather than one query per element.

Practical notes

You may name an Eloquent model instead of a table, and prefix a connection name onto the table. The rule is not implicit, so an absent or empty value skips it — combine with required or nullable deliberately.

Example

Example · php
<?php

use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

// Unscoped: confirms the row exists for anyone who asks.
$loose = Validator::make(['document_id' => 4711], [
    'document_id' => ['required', 'exists:documents,id'],
]);

// Scoped: only rows belonging to the caller's team count.
$safe = Validator::make(['document_id' => 4711], [
    'document_id' => [
        'required',
        Rule::exists('documents', 'id')
            ->where(fn (Builder $query) => $query->where('team_id', $teamId)),
    ],
]);

var_dump($loose->passes());   // bool(true)  - the row exists somewhere
var_dump($safe->passes());    // bool(false) - not in this team
print_r($safe->errors()->all());

// Array
// (
//     [0] => The selected document id is invalid.
// )

When to use it

  • Confirming a submitted category_id refers to a real category before saving.
  • Checking that every tag ID in a submitted array exists, in a single query.
  • Validating a coupon code against the coupons table, scoped to active ones.
  • Verifying a state abbreviation against a lookup table instead of a hard-coded list.

Frequently asked questions

Which column does exists search if I do not name one?

The one matching the field name. `'state' => 'exists:states'` looks for a `states.state` column, which is rarely what you want for an ID field — write `exists:states,abbreviation` or `exists:App\Models\User,id` explicitly.

How do I scope exists so it cannot be used to enumerate records?

Use the fluent form with a where clause: `Rule::exists('documents', 'id')->where(fn ($q) => $q->where('team_id', $teamId))`. Without it the rule answers "does this ID exist?" for any caller, and the distinct error message reveals which IDs are real.

Does exists run one query per array element?

No — when both the `array` rule and `Rule::exists()` are assigned to the same field, Laravel builds a single query covering all the values. Using `field.*` with a per-element rule instead gives better error messages at the cost of one query per element.

More examples

Validating an array of values in one query

When both `array` and `exists` are assigned to the same field, Laravel builds a single query to determine whether all of the given values exist — not one query per element. The trade-off is the message: it reports the field as a whole rather than naming ZZ, so use `states.*` with a per-element rule when you need to point at the offending value.

Example · php
<?php

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

$input = ['states' => ['CA', 'NY', 'ZZ']];

$validator = Validator::make($input, [
    'states' => ['array', Rule::exists('states', 'abbreviation')],
]);

var_dump($validator->passes());
print_r($validator->errors()->all());

// bool(false)
// Array
// (
//     [0] => The selected states is invalid.
// )

Table, column and connection variations

The first line is the one that surprises people: with no column given, the rule searches a column named after the field, so `'state' => 'exists:states'` needs a `states.state` column and quietly throws a QueryException if the column is actually called `abbreviation`.

Example · php
<?php

use Illuminate\Validation\Rule;

$rules = [
    // Column defaults to the field name: looks for states.state
    'state'      => 'exists:states',

    // Explicit column: states.abbreviation
    'state_code' => 'exists:states,abbreviation',

    // A non-default database connection
    'email'      => 'exists:reporting.staff,email',

    // An Eloquent model instead of a table name
    'user_id'    => 'exists:App\Models\User,id',

    // Fluent equivalent of the second line
    'region'     => [Rule::exists('states', 'abbreviation')],
];

print_r(array_keys($rules));

// Array
// (
//     [0] => state
//     [1] => state_code
//     [2] => email
//     [3] => reporting.staff
//     [4] => region
// )

A scoped form request

Both foreign keys are constrained to the caller's team, so a task can never be attached to another tenant's project. The `nullable` on `assignee_id` lets an unassigned task through while still checking the value when one is given, and `withoutTrashed()` keeps archived projects out of the candidate set.

Example · php
<?php

namespace App\Http\Requests;

use Illuminate\Database\Query\Builder;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class StoreTaskRequest extends FormRequest
{
    public function rules(): array
    {
        $teamId = $this->user()->current_team_id;

        return [
            'title'      => ['required', 'string', 'max:255'],
            'project_id' => [
                'required',
                Rule::exists('projects', 'id')
                    ->where(fn (Builder $q) => $q->where('team_id', $teamId))
                    ->withoutTrashed(),
            ],
            'assignee_id' => [
                'nullable',
                Rule::exists('users', 'id')
                    ->where(fn (Builder $q) => $q->where('team_id', $teamId)),
            ],
        ];
    }
}

// print_r($request->validated());
//
// Array
// (
//     [title] => Ship the release
//     [project_id] => 12
//     [assignee_id] =>
// )

Discussion

  • Be the first to comment on this lesson.