Using Form Checkboxes in Laravel Blade (keep old value)

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:

.env
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=

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:

app/Models/Task.php
<?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:

database/migrations/2024_04_20_210302_create_tasks_table.php
<?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:

app/Http/Controllers/TaskController.php
<?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:

routes/web.php
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:

routes/web.php
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 resources/views/tasks/form.blade.php. 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:

resources/views/tasks/index.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">
</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:

Our Example Application Showing an Empty Task List

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.

Our Example Application Create Task Form
Our Example Application Showing a Filled Task List

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

Johan van den Broek

Johan is the creator of laracoding.com. As a child, he began tinkering with various programming languages, many of which have been long forgotten today. Currently, he works exclusively with PHP and Laravel, and his passion for programming remains to this day.

Leave a Reply

Your email address will not be published. Required fields are marked *

Recent Posts