exists
Fails unless the value already exists in the given database table column.
exists:table,columnParameters
| Parameter | Type | Description |
|---|---|---|
$table | string | Table name, or a fully qualified Eloquent model class whose table is used. May be prefixed with a connection name, e.g. `connection.staff`. |
$columnoptional | string= the field name | Column 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
<?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.
<?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`.
<?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.
<?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