Advanced Route Model Binding in Laravel

So, imagine you have a platform on which each user has a list of Invoices, and those invoices have a number starting from 1000 for each user. You might still want to use URLs and endpoints like: /invoices/1005 instead of the primary key in the database. So, naively you set the route key name to use the number column instead of id, thinking everything will be fine. At least, that's what I did.

Columns: id, number, user_id

public function getRouteKeyName()
    return 'number';

It's simple Route Model Binding, with a custom key name.

But now, what happens when two different users try to view their first invoice: /invoices/1000. You'd expect it to show the corresponding invoice, right? Problem is, Laravel does not know by default that it should filter by user_id, so it would simply return the first instance that has a number of 1000. Obviously, this is not intended behavior for our application.

Solving the issue above

What I did, was that I added some custom route model binding, as per the Laravel docs. So I opened my App\Providers\RouteServiceProvider file, and added some stuff to my boot() method:

public function boot()

    Route::bind('invoice', function ($value) {
        return App\Invoice::whereNumber($value)

You'll want to use auth middleware to ensure that there's a logged in user, and also consider using policies and guarding for extra safety.


You could add a global scope to the model which looked for the authenticated user and applied a similar search as above. BUT you would very likely experience a lot of side-effects from doing so, such as:

  • You cannot get the model from Tinker unless you auth before
  • Any attempt to get the model (including collections) will check as well. So doing Invoice::latest()->get() would filter by authenticated user.

I'd recommend custom route model binding as above, and setting up some policies to ensure the authenticated user has access to the model.

Show Comments