Checkboxes are very commonly used in forms on the web. However, they present a unique challenge when handling form submissions.
Unlike other form inputs, checkboxes are missing from the request if left unchecked and present when checked. This requires some special care when storing or updating records. Typically you’ll want to set them to “0” when the option was unchecked, rather than ignoring the users choice!
In this tutorial, we’ll explore how to properly use checkboxes in Blade forms. We’ll be building an example application which stores a tasks completion state. We’ll cover displaying a list of tasks, handling form submissions for storing or updating tasks, implementing proper checkbox validation logic, and effectively handling error messages.
Let’s get started!
Step 1: Set Up Laravel Environment
First, ensure you have Laravel installed. If not, install it using Composer:
composer create-project --prefer-dist laravel/laravel checkbox-form
Navigate into your project directory:
cd checkbox-form
Step 2: Configure Database
For simplicity I recommend using an SQLite Database on your local computer. If you’ve installed Laravel 11 or newer it will use SQLite out of the box. Otherwise you can use it by simply editing the .env
file in your Laravel root folder to:
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
Remember to create the SQLite database file. You can use your editor to create a blank file at database/database.sqlite
, or if you are on Mac or Linux, you can simply run:
touch database/database.sqlite
Step 3: Create Model and Migration
Let’s create a model “Task” and its migration by running:
php artisan make:model Task -m
Step 4: Add Model Code
Now open your Model at app/Models/Task.php
and add the $fillable
array to allow so we can easily assign values when creating new records:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
protected $fillable = [
'name',
'completed'
];
}
Step 5: Add Migration Code
In the generated migration file (database/migrations/yyyy-mm-dd-create_tasks_table.php
), define the necessary fields including a boolean field for our checkbox:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->boolean('completed')->default(false);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('tasks');
}
};
Step 6: Run Migration
Now create the table by running the migrations using Artisan:
php artisan migrate
Step 7: Create Controller
Generate a controller for the Task model:
php artisan make:controller TaskController --resource
Step 8: Add Controller Code
Open the generated controller in app/Http/Controllers/TaskController.php
and add methods to index, create, store, edit and update a task using the code below:
<?php
namespace App\Http\Controllers;
use App\Models\Task;
use Illuminate\Http\Request;
class TaskController extends Controller
{
public function index()
{
$tasks = Task::all();
return view('tasks.index', compact('tasks'));
}
public function create()
{
return view('tasks.form');
}
public function store(Request $request)
{
$request->validate([
'name' => 'required',
'completed' => 'sometimes|in:on,1',
]);
Task::create([
'name' => $request->name,
'completed' => $request->boolean('completed'),
]);
return redirect()->route('tasks.index');
}
public function edit(Task $task)
{
return view('tasks.form', compact('task'));
}
public function update(Request $request, Task $task)
{
$request->validate([
'name' => 'required',
'completed' => 'sometimes|in:on,1',
]);
$task->update([
'name' => $request->name,
'completed' => $request->boolean('completed'),
]);
return redirect()->route('tasks.index');
}
}
A few notes on how it is implemented:
- We use a validation rule “sometimes”
A checkbox is a special case of an input in that it will not be sent with the request if it is unchecked, not even with a value “off” of “0”, it will just be missing - We use a validation rule “in” that allows “1” and “on”
Common browser will pass a string value “on” for a checked box. I chose to allow “on” and also “1” as it is also commonly used - When storing or updating we use $request->boolean(‘completed’)
The method$request->boolean(..)
is provided by Laravel and will evaluate both “1” or “on” to a simple boolean value:true
Step 9: Define Routes
In routes/web.php
, define routes for tasks:
use App\Http\Controllers\TaskController;
use Illuminate\Support\Facades\Route;
// Define routes for tasks excluding show and delete
Route::get('tasks', [TaskController::class, 'index'])->name('tasks.index');
Route::get('tasks/create', [TaskController::class, 'create'])->name('tasks.create');
Route::post('tasks', [TaskController::class, 'store'])->name('tasks.store');
Route::get('tasks/{task}/edit', [TaskController::class, 'edit'])->name('tasks.edit');
Route::put('tasks/{task}', [TaskController::class, 'update'])->name('tasks.update');
Alternatively you may also use the shorthand version to achieve the same:
use App\Http\Controllers\TaskController;
use Illuminate\Support\Facades\Route;
// Define routes for tasks excluding show and delete
Route::resource('tasks', TaskController::class)->except(['show', 'destroy']);
Step 10: Create Blade View With Form
Now we’ll create a Blade view to use for both the create and edit form.
Open your
and add:resources/views/tasks/form.blade.php
.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo App</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<form method="post" action="{{ isset($task) ? route('tasks.update', $task->id) : route('tasks.store') }}">
@csrf
@isset($task)
@method('put')
@endisset
<div class="mb-3">
<label for="name" class="form-label">Task Name</label>
<input type="text" class="form-control"
id="name"
name="name"
value="{{ old('name', isset($task) ? $task->name : '') }}"
>
@error('name')
<div class="text-danger">{{ $message }}</div>
@enderror
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input"
id="completed"
name="completed"
{{ old('completed', isset($task) ? ($task->completed ? 'checked' : '') : '') }}
>
<label class="form-check-label" for="completed">Completed</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</body>
</html>
A few notes on how this view is implemented:
- We set @method PUT in case we are updating an existing task
This will ensure it uses the PUT route that leads to the Controller update method while for store it will use the default, which is POST - We add @csrf in the form
This will make sure Laravel can verify the form submission originated from your application - We use the helper function
old()
This ensures we read and repopulate the value that the user picked in case the form is shown a second time - We read and use the value in $task->name and $task->completed in case there is no value from a previous submission found with
old()
Step 11: Create Blade View For Index
Create a view file at resources/views/tasks/index.blade.php
which displays tasks in a table by using the code below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo App</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<table class="table">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Completed</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@forelse($tasks as $task)
<tr>
<td>{{ $task->id }}</td>
<td>{{ $task->name }}</td>
<td>{{ $task->completed ? 'Yes' : 'No' }}</td>
<td>
<a href="{{ route('tasks.edit', $task->id) }}" class="btn btn-primary">Edit</a>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center">No tasks found.</td>
</tr>
@endforelse
</tbody>
</table>
<a href="{{ route('tasks.create') }}" class="btn btn-success">Create Task</a>
</div>
</body>
</html>
Step 12: Run and Test the application
To start the application run the following Artisan command:
php artisan serve
Then open your browser and navigate to: http://127.0.0.1:8000/tasks which will look like this:
The “Create Task” button will lead to the form where you can enter a Name and use a checkbox to set or unset “Completed”
Try out the form to save a completed and uncompleted task. You should be able to verify it works correctly and look like below. You can also test updating a task using the “Edit” button.
That’s it! You’ve successfully created an application that handles checkbox values properly.
Conclusion
This tutorial has demonstrated how to use checkboxes in Laravel Blade forms. By understanding the nuances of checkbox handling, such as their absence from the request when unchecked, we’ve learned how to ensure correctly storing and updating records.
I hope this tutorial has been helpful to you. Feel free to let me know in the comments below how you are using checkboxes in your application and ask any questions you might have after reading.
Happy coding!
References
- Requests – Retrieving Boolean Input Values (Laravel Documentation)
- Requests – Retrieving Old Input (Laravel Documentation)
- CSRF Protection (Laravel Documentation)
This entry is part 6 of 9 in the series Working With Forms
- Sending a Form to a PUT Route in Laravel (With Example)
- Sending a Form to a DELETE Route in Laravel (With Example)
- Why Use Laravel Form Request and How? (BIG Code improvement)
- Making a File Upload Form in Laravel: a Step-by-Step Guide
- How to Insert Form Array Values Into the Database in Laravel
- Using Form Checkboxes in Laravel Blade (keep old value)
- Using Form Select in Laravel Blade (keep old value)
- Using Form Radio Buttons in Laravel Blade (keep old value)
- Using Text and Textarea in Laravel Blade (keep old value)