Lesson 48 of 50
An admin dashboard is the main control panel of a web application. It allows authorized administrators to monitor statistics and manage application data.
Our dashboard will include:
Technologies:
We will build a dashboard for a student management application. The examples use a Student model and a Course model. Adjust the model names and database fields to match your own application.
Generate a controller using Artisan:
php artisan make:controller AdminDashboardController
Open
app/Http/Controllers/AdminDashboardController.php.
<?php
namespace App\Http\Controllers;
use App\Models\Student;
use App\Models\Course;
use Illuminate\Support\Facades\DB;
class AdminDashboardController extends Controller
{
public function index()
{
$totalStudents = Student::count();
$totalCourses = Course::count();
$totalCollected = DB::table('payments')
->where('status', 'paid')
->sum('amount');
$pendingFees = DB::table('student_fees')
->where('status', 'pending')
->sum('amount');
$recentStudents = Student::latest()
->take(5)
->get();
return view('admin.dashboard', compact(
'totalStudents',
'totalCourses',
'totalCollected',
'pendingFees',
'recentStudents'
));
}
}
This example assumes your application has
students, courses,
payments, and student_fees
tables, and that payment records use a
status field with the value
paid.
Update the model names, table names, and column names if your database uses a different structure.
Open routes/web.php.
Add the following route:
use App\Http\Controllers\AdminDashboardController;
use Illuminate\Support\Facades\Route;
Route::get(
'/admin/dashboard',
[AdminDashboardController::class, 'index']
)->name('admin.dashboard');
The route connects the dashboard URL to the
index() method of the controller.
To check the registered route, run:
php artisan route:list --name=admin.dashboard
Important: this route is not protected by authentication yet. We will discuss how to restrict access in the security section.
Create this directory if it does not exist:
resources/views/admin
Create resources/views/admin/dashboard.blade.php:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Admin Dashboard</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet">
</head>
<body class="bg-light">
<nav class="navbar navbar-dark bg-dark">
<div class="container-fluid">
<span class="navbar-brand">Student Management Admin</span>
</div>
</nav>
<div class="container-fluid py-4">
<h2 class="mb-4">Admin Dashboard</h2>
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
<div class="row g-4">
<div class="col-md-6 col-xl-3">
<div class="card border-primary shadow-sm h-100">
<div class="card-body">
<h6 class="text-muted">Total Students</h6>
<h2 class="text-primary">
{{ number_format($totalStudents) }}
</h2>
<a href="{{ url('/students') }}"
class="btn btn-outline-primary btn-sm">
Manage Students
</a>
</div>
</div>
</div>
<div class="col-md-6 col-xl-3">
<div class="card border-success shadow-sm h-100">
<div class="card-body">
<h6 class="text-muted">Total Courses</h6>
<h2 class="text-success">
{{ number_format($totalCourses) }}
</h2>
<a href="{{ url('/courses') }}"
class="btn btn-outline-success btn-sm">
Manage Courses
</a>
</div>
</div>
</div>
<div class="col-md-6 col-xl-3">
<div class="card border-info shadow-sm h-100">
<div class="card-body">
<h6 class="text-muted">Fees Collected</h6>
<h2 class="text-info">
₹{{ number_format($totalCollected, 2) }}
</h2>
<a href="{{ url('/payments') }}"
class="btn btn-outline-info btn-sm">
View Payments
</a>
</div>
</div>
</div>
<div class="col-md-6 col-xl-3">
<div class="card border-danger shadow-sm h-100">
<div class="card-body">
<h6 class="text-muted">Pending Fees</h6>
<h2 class="text-danger">
₹{{ number_format($pendingFees, 2) }}
</h2>
<a href="{{ url('/fees') }}"
class="btn btn-outline-danger btn-sm">
Manage Fees
</a>
</div>
</div>
</div>
</div>
<div class="card shadow-sm mt-5">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">Recent Student Registrations</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-bordered mb-0">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>Student Name</th>
<th>Email</th>
<th>Course</th>
</tr>
</thead>
<tbody>
@forelse ($recentStudents as $student)
<tr>
<td>{{ $student->id }}</td>
<td>{{ $student->name }}</td>
<td>{{ $student->email }}</td>
<td>{{ $student->course ?? 'N/A' }}</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center">
No students registered yet.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</body>
</html>
The dashboard displays four summary cards and a table containing the five most recently registered students.
The example links to /courses,
/payments, and /fees are
placeholders; create those routes before using them.
Dashboard statistics are calculated from database records. Common Eloquent methods include:
// Count all students
$totalStudents = Student::count();
// Count students admitted today
$todayStudents = Student::whereDate(
'created_at',
today()
)->count();
// Get the five latest students
$recentStudents = Student::latest()
->take(5)
->get();
// Count students enrolled in a particular course
$adcaStudents = Student::where(
'course',
'ADCA'
)->count();
You can display these values in additional dashboard cards.
Example:
<div class="card border-warning shadow-sm">
<div class="card-body">
<h6>Today's Admissions</h6>
<h3 class="text-warning">
{{ number_format($todayStudents) }}
</h3>
</div>
</div>
If you add this card, remember to calculate
$todayStudents in the controller and
pass it to the view.
A dashboard can also show how much money was collected each month.
For this example, assume the payments
table contains amount,
status, and created_at
columns.
Add this query inside the controller:
use Illuminate\Support\Facades\DB;
$monthlyCollections = DB::table('payments')
->selectRaw('MONTH(created_at) AS month_number')
->selectRaw('SUM(amount) AS total_amount')
->where('status', 'paid')
->whereYear('created_at', now()->year)
->groupByRaw('MONTH(created_at)')
->orderByRaw('MONTH(created_at)')
->get();
Include $monthlyCollections in the
controller's compact() call.
Display the results in Blade:
<div class="card border-info shadow-sm mt-4">
<div class="card-header bg-info text-white">
Monthly Fee Collection
</div>
<div class="card-body">
<table class="table table-bordered">
<thead>
<tr>
<th>Month Number</th>
<th>Collected Amount</th>
</tr>
</thead>
<tbody>
@forelse ($monthlyCollections as $collection)
<tr>
<td>{{ $collection->month_number }}</td>
<td>
₹{{ number_format($collection->total_amount, 2) }}
</td>
</tr>
@empty
<tr>
<td colspan="2">No collections found.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
This query uses MySQL month functions. If your payment
date is stored in a separate column, use that column
instead of created_at.
An admin dashboard should not be accessible to every visitor. Protect it with authentication and, where appropriate, an admin authorization check.
If your project has authentication configured, a basic authenticated route can be written as follows:
use App\Http\Controllers\AdminDashboardController;
use Illuminate\Support\Facades\Route;
Route::middleware('auth')->group(function () {
Route::get(
'/admin/dashboard',
[AdminDashboardController::class, 'index']
)->name('admin.dashboard');
});
The auth middleware requires the visitor
to be authenticated. It does not, by itself, prove
that the user is an administrator.
For an application with multiple user roles, create an authorization policy, gate, or admin middleware that checks the authenticated user's role.
Example concept:
// Example only: requires a role field and
// appropriate authentication setup.
if (! auth()->user()->is_admin) {
abort(403);
}
Place authorization checks in appropriate middleware, policies, or controller authorization logic rather than relying only on hiding dashboard links.
Some useful dashboard design improvements include:
Example of a reusable Blade component:
<x-dashboard-card
title="Total Students"
:value="$totalStudents"
color="primary"
/>
This is an example of how a custom Blade component might be used. You must create the component and define its accepted properties before using it.
Build your own Laravel admin dashboard.
Bonus Task:
Add a year filter to the fee report and display the selected year's monthly collection totals.
1. What is the main purpose of an admin dashboard?
2. Which Eloquent method counts database records?
3. Does the auth middleware alone guarantee that a user is an administrator?