Laravel error
Laravel: Attempt to read property on null
The error
Attempt to read property "name" on nullPHP 8 turned a notice into a warning and a great deal of code into visible breakage. The message names the property, not the reason it is missing, and the reason is what you need.
The error
ErrorException
Attempt to read property "name" on null
In PHP 7 the same code produced a notice, an empty string, and a page that looked almost right. PHP 8 made it a warning, which in a Laravel application with strict error handling becomes an exception and a 500.
The code did not get worse. It got visible.
What it means
Something was null, and a property was read from it. That is the whole
message, and it names the property rather than the variable - so Attempt to read property "name" on null tells you a ->name was read, not which object
was missing.
The useful question is why it was null, and there are four distinct answers with four different fixes.
1. The record does not exist
$user = User::find($id);
echo $user->name;find() returns null when nothing matches. If the record is genuinely
expected to be there, say so and get a 404 instead of a 500:
$user = User::findOrFail($id);Route model binding does this for you, which is one reason to prefer it:
Route::get('/users/{user}', fn (User $user) => view('profile', compact('user')));2. The relation is not loaded
$order = Order::find($id); // no eager load
echo $order->customer->name; // null, if lazy loading is disabledThis is the one that confuses people, because the customer does exist. What does not exist is the loaded relation. With lazy loading disabled - as it should be outside production - accessing it returns null rather than issuing a query.
The fix is to load it where the model is retrieved:
$order = Order::with('customer')->findOrFail($id);3. The relation exists and is empty
A hasOne or belongsTo that is legitimately optional - an order with no
assigned agent, a user with no profile yet. Here null is correct and the code
has to handle it:
{{ $order->agent?->name ?? 'Unassigned' }}This is where the null-safe operator belongs. Using it everywhere, as a reflex against this error, is how the first two cases get hidden instead of fixed.
4. The chain is longer than you think
$invoice->order->customer->address->cityAny link can be null, and the message names only the last property read. Four possible causes, one message.
Where a chain like this appears in a template, the better answer is usually to stop building it there - a computed attribute or a view model resolves it once, in one place, where it can be tested.
Finding the actual line
The exception is thrown where the property was read, which in a Blade
template means a compiled view path rather than your source. Look further down
the stack trace for the .blade.php file, or set a breakpoint on the compiled
line - the top frame is rarely the useful one.
In production, where the page is a generic error, the log entry has the same trace. If it does not have enough context to identify the record, that is worth fixing separately: an exception with no id in it is an exception nobody can reproduce.
Preventing the category
Three habits remove most of these permanently.
findOrFail and route model binding, so a missing record is a 404 rather than
a null travelling into a template. Lazy loading disabled outside production,
so an unloaded relation fails in a test rather than rendering blank. And
nullable columns that the application never expects to be null made
non-nullable in the schema, so the impossible state is actually impossible.
The same message inside a queued job usually has a different cause. The property was not null when the job was dispatched; it was dropped on the way to the worker, which makes it a serialisation problem.
Most of that list is schema work, and fixing nullability on a table that already has data takes planning. The lazy-loading habit has an exception of its own: Laravel will name the unloaded relation if you let it.
