Introduction:
Hi there! In this blog, I will be showing you how to build a simple CRUD application using Laravel 13. CRUD is an acronym for CREATE, READ, UPDATE, DELETE, the four basic operations you will find in almost every web application, from a to-do list to a full admin dashboard.
If you followed my Laravel 12 CRUD tutorial, you will feel right at home. The flow is the same, but I have updated everything for Laravel 13 and cleaned up the code: we will use route model binding, mass assignment with the new #[Fillable] PHP attribute, validation that returns the validated data, and pagination on the list page.
Laravel is a free, open-source PHP web framework for building web applications that follow the MVC (Model-View-Controller) architectural pattern. It ships with a lot of built-in features like routing, the Eloquent ORM, migrations, validation, and the Blade templating engine, so you can build apps faster and with less boilerplate. Laravel 13 was released on March 17, 2026. It focuses on AI-native features, more PHP attributes, and stronger defaults while keeping breaking changes to a minimum.
By the end of this tutorial, we will have a small Project Manager app where we can create, list, view, edit, and delete projects.
Prerequisite:
Before we proceed, make sure these are installed and updated on your local machine:
- PHP >= 8.3 (Laravel 13 no longer supports PHP 8.2)
- Composer >= 2.8
- Laravel Installer >= 5.x (latest)
- Node >= 22 and NPM (needed for the default Vite frontend build)
- MySQL or MariaDB (you can also use the default SQLite)
You can check your PHP version by running:
php -v
If you need to update Composer, run:
composer self-update
If you need to update the Laravel Installer, run:
composer global require laravel/installer
Don’t have PHP and Composer yet? Laravel’s official php.new script installs PHP, Composer, and the Laravel Installer in one go. For a beginner walkthrough, you can also read my Laravel installation guide.
Step 1: Install Laravel 13
Open your Terminal or CMD and run this command to create a new Laravel 13 project:
laravel new laravel-13-crud
The installer will ask you a few questions. For this tutorial, I chose these options:
- Starter kit: None (we will build our own simple Blade views)
- Testing framework: Pest or PHPUnit, whichever you like
- Laravel Boost: optional (it helps AI coding tools, but it isn’t needed here)
- Database: MySQL
- Run the default database migrations: Yes
- Run npm install and npm run build: Yes
Once it is done, go inside the project folder:
cd laravel-13-crud
Step 2: Set Database Configuration
If you chose MySQL during installation, Laravel already filled in most of the database settings. You can always change them by opening the .env file in the project root folder and updating the database configuration:
.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_13_crud
DB_USERNAME=root
DB_PASSWORD=
Make sure the database (for example laravel_13_crud) exists in your MySQL server. If you would rather skip MySQL, leave DB_CONNECTION=sqlite. Laravel creates the database/database.sqlite file for you, and the rest of this tutorial works exactly the same.
Step 3: Create Model and Database Migration
Model is a class that represents a database table. We use it to query and save data.
Migration is like version control for your database. It lets you define and share your database schema with your team.
Run this command to create a Project model together with its migration:
php artisan make:model Project --migration
This creates two files. First, open the new migration file inside database/migrations/ (it ends with _create_projects_table.php) and add the name and description columns:
database/migrations/xxxx_xx_xx_xxxxxx_create_projects_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('projects', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('projects');
}
};
Next, open the model and tell Laravel which fields can be mass assigned. Laravel 13 lets us do this with the #[Fillable] PHP attribute right above the class, instead of the old protected $fillable property (the property still works if you prefer it):
app/Models/Project.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
#[Fillable(['name', 'description'])]
class Project extends Model
{
//
}
Now run the migration to create the projects table:
php artisan migrate
Step 4: Create a Resource Controller
A resource controller is a controller that already has a method for each CRUD action: index, create, store, show, edit, update, and destroy. If you want to understand controllers more deeply, check out my Laravel controllers explained guide.
Run this command to create a resource controller bound to our Project model:
php artisan make:controller ProjectController --resource --model=Project
Passing --model=Project type-hints the Project model in the methods, so Laravel’s route model binding automatically finds the record for us (and returns a 404 page if it doesn’t exist). Open the generated file and replace its contents with this code:
app/Http/Controllers/ProjectController.php
<?php
namespace App\Http\Controllers;
use App\Models\Project;
use Illuminate\Http\Request;
class ProjectController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$projects = Project::latest()->paginate(10);
return view('projects.index', ['projects' => $projects]);
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('projects.create');
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|max:255',
'description' => 'required',
]);
Project::create($validated);
return redirect()->route('projects.index')
->with('success', 'Project saved successfully!');
}
/**
* Display the specified resource.
*/
public function show(Project $project)
{
return view('projects.show', ['project' => $project]);
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Project $project)
{
return view('projects.edit', ['project' => $project]);
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Project $project)
{
$validated = $request->validate([
'name' => 'required|max:255',
'description' => 'required',
]);
$project->update($validated);
return redirect()->route('projects.index')
->with('success', 'Project updated successfully!');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Project $project)
{
$project->delete();
return redirect()->route('projects.index')
->with('success', 'Project deleted successfully!');
}
}
A few things to notice here:
$request->validate()checks the input and returns only the validated fields. If validation fails, Laravel automatically redirects back with the errors and the old input.Project::create($validated)and$project->update($validated)work because we allowed those fields with#[Fillable].paginate(10)shows 10 projects per page, so the list stays fast as your data grows.
Step 5: Register a Resourceful Route
Now let’s register the routes. Open routes/web.php and update it:
routes/web.php
<?php
use App\Http\Controllers\ProjectController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return redirect()->route('projects.index');
});
Route::resource('projects', ProjectController::class);
That single Route::resource() line creates all seven CRUD routes for us. I also made the home page redirect to the project list. You can see all the generated routes by running:
php artisan route:list --path=projects
Want to learn more about routes? Read my Laravel routing tutorial for beginners.
Step 6: Create Blade Files
Blade is Laravel’s simple but powerful templating engine. Blade files use the .blade.php extension and live in the resources/views/ folder. To keep things simple, I am loading Bootstrap 5 from a CDN, so we don’t need to touch Vite for this tutorial.
Create a folder named projects inside resources/views/, then create these Blade files inside it:
- layout.blade.php
- index.blade.php
- create.blade.php
- edit.blade.php
- show.blade.php
Then add the code below to each file.
resources/views/projects/layout.blade.php
This is the main layout. The other pages extend it.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel 13 Project Manager</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container py-5">
@yield('content')
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
resources/views/projects/index.blade.php
This page lists all projects with Show, Edit, and Delete buttons, plus pagination links.
@extends('projects.layout')
@section('content')
<h2 class="text-center mb-4">Laravel 13 Project Manager</h2>
<div class="card">
<div class="card-header">
<a class="btn btn-outline-primary" href="{{ route('projects.create') }}">
Create New Project
</a>
</div>
<div class="card-body">
@if (session('success'))
<div class="alert alert-success">
<b>{{ session('success') }}</b>
</div>
@endif
<table class="table table-bordered align-middle">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th width="260px">Action</th>
</tr>
</thead>
<tbody>
@forelse ($projects as $project)
<tr>
<td>{{ $project->name }}</td>
<td>{{ Str::limit($project->description, 80) }}</td>
<td>
<form action="{{ route('projects.destroy', $project) }}" method="POST">
<a class="btn btn-outline-info" href="{{ route('projects.show', $project) }}">Show</a>
<a class="btn btn-outline-success" href="{{ route('projects.edit', $project) }}">Edit</a>
@csrf
@method('DELETE')
<button type="submit" class="btn btn-outline-danger"
onclick="return confirm('Are you sure you want to delete this project?')">
Delete
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="3" class="text-center">No projects yet.</td>
</tr>
@endforelse
</tbody>
</table>
{{ $projects->links('pagination::bootstrap-5') }}
</div>
</div>
@endsection
resources/views/projects/create.blade.php
This page shows the form for adding a new project, along with any validation errors.
@extends('projects.layout')
@section('content')
<h2 class="text-center mb-4">Create New Project</h2>
<div class="card">
<div class="card-header">
<a class="btn btn-outline-info" href="{{ route('projects.index') }}">
View All Projects
</a>
</div>
<div class="card-body">
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('projects.store') }}" method="POST">
@csrf
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" value="{{ old('name') }}">
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" rows="3" name="description">{{ old('description') }}</textarea>
</div>
<button type="submit" class="btn btn-outline-primary mt-2">Save Project</button>
</form>
</div>
</div>
@endsection
resources/views/projects/edit.blade.php
This page shows the edit form. Notice the @method('PUT') directive, because HTML forms only support GET and POST.
@extends('projects.layout')
@section('content')
<h2 class="text-center mb-4">Edit Project</h2>
<div class="card">
<div class="card-header">
<a class="btn btn-outline-info" href="{{ route('projects.index') }}">
View All Projects
</a>
</div>
<div class="card-body">
@if ($errors->any())
<div class="alert alert-danger">
<ul class="mb-0">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('projects.update', $project) }}" method="POST">
@csrf
@method('PUT')
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" value="{{ old('name', $project->name) }}">
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea class="form-control" id="description" rows="3" name="description">{{ old('description', $project->description) }}</textarea>
</div>
<button type="submit" class="btn btn-outline-success mt-2">Update Project</button>
</form>
</div>
</div>
@endsection
resources/views/projects/show.blade.php
This page displays the details of a single project.
@extends('projects.layout')
@section('content')
<h2 class="text-center mb-4">Show Project</h2>
<div class="card">
<div class="card-header">
<a class="btn btn-outline-info" href="{{ route('projects.index') }}">
View All Projects
</a>
</div>
<div class="card-body">
<b class="text-muted">Name:</b>
<p>{{ $project->name }}</p>
<b class="text-muted">Description:</b>
<p>{{ $project->description }}</p>
<b class="text-muted">Created:</b>
<p>{{ $project->created_at->format('M d, Y h:i A') }}</p>
</div>
</div>
@endsection
Step 7: Run the Application
Everything is ready! Start the development server by running:
php artisan serve
Or, if you want to run the Laravel server, queue worker, and Vite together, use the dev Composer script:
composer run dev
Now open your browser and go to:
http://127.0.0.1:8000/projects
Try it out: create a project, view it, edit it, and delete it. Also try submitting the form with empty fields, and you will see the validation messages appear above the form.
Conclusion:
And that’s it! We have built a simple CRUD app with Laravel 13 using a model and migration, a resource controller with route model binding and validation, a resourceful route, and Blade views. This is the foundation of almost every Laravel project, so once you are comfortable with it, you can add authentication, file uploads, search, or turn it into a REST API.
I hope this tutorial helped you get started with Laravel 13. If you run into any issues, feel free to leave a comment below. Happy coding!
