Model::find

Looks up a model by its primary key, returning the model or null when no row matches.

Signature
public function find(mixed $id, array|string $columns = ['*']): \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|null

Parameters

ParameterTypeDescription
$idmixedThe primary key value to look up. An array or Arrayable of keys switches the method to multi-key mode.
$columnsoptionalarray|string= ['*']Columns to select on the hydrated model.

Return value

\Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection|nullThe matching model, or null when nothing matches. When $id is an array or Arrayable, the call is delegated to findMany() and an Eloquent Collection is returned instead.

Errors

Does not throw when the row is missing; it returns null. Use findOrFail() when a missing row should raise ModelNotFoundException.

find() builds a where key = ? clause against the model's primary key and returns the first match. It is the idiomatic lookup for anything you already hold an identifier for — a route parameter, a foreign key, a job payload.

Return shape changes with the argument

Pass a scalar and you get a model or null. Pass an array or any Arrayable and the call is forwarded to findMany(), which returns an Eloquent Collection. This is the single most common surprise: code written for a scalar breaks the moment a caller hands it an array of ids, because the null check silently passes on a collection.

What trips people up

find() respects the model's getKeyName(), so on a model with a custom primary key you are not querying id. It also applies global scopes — on a soft-deleting model, a trashed row returns null even though the row is physically present. Reach for withTrashed()->find($id) when you need it back.

Because the result may be null, chaining straight onto it is unsafe. User::find($id)->email raises a fatal Attempt to read property on null the first time an id goes stale. Either guard the result, use ?->, or switch to findOrFail().

Example

Example · php
<?php

use App\Models\Flight;

// Scalar id -> a Flight instance or null.
$flight = Flight::find(1);

if ($flight === null) {
    echo "No flight 1\n";
} else {
    echo $flight->name, "\n";
}

// Array of ids -> an Eloquent Collection, never null.
$flights = Flight::find([1, 2, 3]);

echo get_class($flights), "\n";
// Illuminate\Database\Eloquent\Collection

print_r($flights->pluck('name')->all());

When to use it

  • Resolve a route parameter such as /flights/{id} into a model inside a controller.
  • Rehydrate a model inside a queued job from an id stored on the payload.
  • Look up a related record from a foreign key you already hold without loading the relationship.

Frequently asked questions

Why does Model::find() return null instead of throwing?

Because a missing row is a normal, expected outcome for a plain lookup — find() is the non-throwing variant by design. When a missing record should abort the request, use findOrFail(), which raises ModelNotFoundException and is rendered as a 404 response. Use findOr($id, $callback) when you want to supply your own fallback value instead.

Can I pass an array of ids to Model::find()?

Yes, and doing so changes the return type: an array or Arrayable is forwarded to findMany(), which returns an Eloquent Collection rather than a single model or null. The collection contains only the ids that matched, so it can be shorter than the array you passed — check its count if every id is required, or use findOrFail() with an array, which throws unless all of them are found.

Why does find() return null for a row I can see in the database?

Almost always a global scope, most often SoftDeletes filtering on a non-null deleted_at column. Call withTrashed()->find($id) to bypass it, or withoutGlobalScopes() to bypass every scope. The other cause is a custom $primaryKey: find() queries whatever getKeyName() returns, so on a model keyed by uuid or a code column, passing an integer id matches nothing.

More examples

Handling the null result safely

All three safe forms print "missing" for an id that does not exist. The commented first line is the crash people actually file bugs about, because it only fails once a real id goes stale in production.

Example · php
<?php

use App\Models\User;

$id = 999;

// Fatal on a missing row: Attempt to read property on null.
// echo User::find($id)->email;

// Guard clause.
$user = User::find($id);

if (! $user) {
    echo "missing\n";
} else {
    echo $user->email, "\n";
}

// Nullsafe operator - prints nothing rather than crashing.
echo User::find($id)?->email ?? 'missing', "\n";

// Or supply a fallback with findOr().
echo User::findOr($id, fn () => 'missing'), "\n";

Custom keys and soft deletes

The airport lookup shows find() following $primaryKey rather than assuming id. The two var_dumps show the same physical row being absent and then present: the SoftDeletes global scope filters it out until withTrashed() removes that scope.

Example · php
<?php

use App\Models\Post;
use App\Models\Airport;

class Airport extends Model
{
    protected $primaryKey = 'iata_code';
    protected $keyType = 'string';
    public $incrementing = false;
}

// Queries iata_code, not id.
echo Airport::find('LHR')?->name, "\n";

// A soft-deleted post is invisible to find().
var_dump(Post::find(42));            // NULL, even though the row exists

var_dump(Post::withTrashed()->find(42) !== null);  // bool(true)

Selecting columns and eager loading together

The first block shows a partially hydrated model: an unselected column reads as null rather than raising an error, which quietly corrupts downstream logic. The second shows that find() is a normal builder terminator, so with() and withCount() constraints applied beforehand are honoured.

Example · php
<?php

use App\Models\Order;

// Only two columns are hydrated onto the model.
$order = Order::find(17, ['id', 'total_cents']);

echo $order->total_cents, "\n";
var_dump($order->customer_name);   // NULL - never selected

// find() sits at the end of a builder chain too.
$order = Order::with('items.product')
    ->withCount('items')
    ->find(17);

echo $order->items_count, "\n";

foreach ($order->items as $item) {
    echo $item->product->name, "\n";
}

Discussion

  • Be the first to comment on this lesson.