Laravel Tutorial · Laravel Basics · Lesson 4 of 27

Route Parameters and Model Binding

Capture parts of the URL, make them optional, constrain them with patterns, and let Laravel load Eloquent models from IDs automatically.

Create a free account to save this page, take notes and track your progress. Sign in

Required and optional parameters

Wrap a URL segment in braces to capture it: /posts/{id}. Add ? and a default value for optional ones: /greet/{name?}.

Constraints reject values that don't match a pattern: ->whereNumber('id'), ->whereAlpha('name'), ->where('slug', '[a-z0-9-]+'). A request that doesn't match gets a 404 instead of reaching your code.

Parameters, optional values and constraints
<?php
use Illuminate\Support\Facades\Route;

Route::get('/posts/{id}', fn (int $id) => "Post #$id")->whereNumber('id');
Route::get('/greet/{name?}', fn (string $name = 'stranger') => "Hello, $name!");
Route::get('/tags/{tag}', fn (string $tag) => "Tag: $tag")->where('tag', '[a-z0-9-]+');

visit('GET', '/posts/15');
visit('GET', '/posts/abc');        // fails the number constraint: 404
visit('GET', '/greet');
visit('GET', '/greet/Ana');
visit('GET', '/tags/laravel-13');

Output

GET /posts/15 -> 200 Post #15
GET /posts/abc -> 404 {
    "message": "The route posts/abc could not be found."
}
GET /greet -> 200 Hello, stranger!
GET /greet/Ana -> 200 Hello, Ana!
GET /tags/laravel-13 -> 200 Tag: laravel-13

Run this on your computer — see “Get Started” for setup.

Route model binding

If a parameter is type-hinted with an Eloquent model, Laravel looks it up for you: /posts/{post} with Post $post runs Post::findOrFail($id). A missing record becomes a 404 automatically. Use {post:slug} to look up by another column.

Route model binding
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Schema;

Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('slug')->unique();
    $table->string('title');
});

class Post extends Model
{
    public $timestamps = false;
    protected $guarded = [];
}

Post::create(['slug' => 'hello-laravel', 'title' => 'Hello Laravel']);

Route::get('/posts/{post}', fn (Post $post) => $post);                  // looked up by id
Route::get('/blog/{post:slug}', fn (Post $post) => $post->title);       // looked up by slug

visit('GET', '/posts/1');
visit('GET', '/blog/hello-laravel');
visit('GET', '/blog/missing');

Output

GET /posts/1 -> 200 {"id":1,"slug":"hello-laravel","title":"Hello Laravel"}
GET /blog/hello-laravel -> 200 Hello Laravel
GET /blog/missing -> 404 {
    "message": "No query results for model [Post] missing"
}

Run this on your computer — see “Get Started” for setup.

Test yourself

What happens when a route-model-bound record doesn't exist?

  1. The parameter is null
  2. Laravel returns a 404 response
  3. A 500 error
  4. An empty model is created
Show answer

Answer: Laravel returns a 404 response

Was this page helpful?