Skip to content

Laravel error

Laravel: Target class [Controller] does not exist

The error

Target class [PostController] does not exist.

A BindingResolutionException naming a controller means the container built a class from a string and found nothing there. Four ways to arrive at it, with four different fixes.

The error

Illuminate\Contracts\Container\BindingResolutionException

Target class [PostController] does not exist.

What it means

Something asked the service container for a class by name, and the container could not find a class by that name. It is not a routing error, even though a route is usually what triggered it - by the time this is thrown, the router has already handed a string to the container and stepped out of the way.

The name in the brackets is the exact string that was looked up. That is the most useful thing on the page, because comparing it to the class's real fully-qualified name usually ends the investigation.

The four causes

The route names the controller in short form. Until Laravel 8 the router prepended App\Http\Controllers to controller strings. It no longer does, so a route carried across from an older codebase now asks for a class in the global namespace:

// asks the container for \PostController
Route::get('/posts', 'PostController@index');
 
// asks for the class you actually wrote
Route::get('/posts', [\App\Http\Controllers\PostController::class, 'index']);

The second form is worth adopting even where the first still works, because the class name is now a symbol rather than a string: rename the controller and your editor updates the route, instead of a request finding out at three in the morning.

The namespace and the folder disagree. A file at app/Http/Controllers/Admin/PostController.php must declare namespace App\Http\Controllers\Admin;. Move a file between folders without editing its first lines and autoloading stops finding it, while the file sits there looking correct.

Composer's map is stale. The autoloader works from a generated index. Files created outside your editor's project tooling, or after a checkout that skipped an install, are not in it:

composer dump-autoload

Case. PostsController against PostController, or postController in a route. Local development on a case-insensitive filesystem finds the file anyway; the Linux server it deploys to does not, which is why this class of mistake is so reliably a production-only error.

Reading the message properly

The bracketed name tells you which cause you have before you change anything:

What the message saysWhat it means
Target class [PostController]No namespace at all - a short-form route string
Target class [App\Http\Controllers\PostController]The name is right, so the file, its namespace or the autoload map is wrong
Target class [App\Contracts\PaymentGateway]An interface with no binding

The interface case

This one is not a typo and dump-autoload will not touch it. The container can construct a concrete class it can reflect on, but an interface has no implementation to instantiate - it needs to be told:

// AppServiceProvider::register()
$this->app->bind(PaymentGateway::class, StripeGateway::class);

If the binding exists and you still see the error, check that the provider is registered, and that the binding is in register() rather than boot() - something resolved early enough during boot will look for it before boot() has run.

Stop it happening again

Use the array or first-class callable syntax for every route, and keep composer dump-autoload -o in the deploy script. The first turns a whole category of runtime failures into problems your editor points at; the second means a deploy never serves a stale map.

composer dump-autoload -o is one of a short list of things a deploy has to do in the right order, and the list is short enough to write down. Get the order wrong and the errors look unrelated to each other. The Vite manifest error comes from this same mistake.

Related questions

Why did this start after upgrading from Laravel 7?
Because Laravel 8 removed the automatic controller namespace prefix. Before the upgrade a route could name a controller in short form and the router would prepend the namespace for you. Afterwards the string is taken literally, so every route written in short form now points at a class in the global namespace, where none of your controllers live.
Is the array syntax actually better, or just newer?
It is better for a reason that outlives the fashion: a first-class callable is resolved by the compiler and by your editor, so a renamed or deleted controller fails where you renamed it rather than on the request that happens to hit that route. A string is checked only when the route runs.
The class exists and I can see the file. Why can it not be found?
Then the problem is not the class - it is the map from name to file. Composer builds that map at install time. A file added by hand, a namespace that does not match the folder, or a case difference that your Mac tolerates and the server does not will each produce this.
I am getting it for an interface, not a controller.
Then nothing is bound. The container can build a concrete class it can find by reflection, but an interface has no constructor to call - somebody has to tell it which implementation to use. That is a binding in a service provider, not a missing file.
Call us+1 848 272 7583WhatsApp+90 850 308 5436Emailinfo@codefacture.comContact page