In this tutorial, we’ll create a full-featured Admin Dashboard using PHP 8, Bootstrap 5, and MySQL (via XAMPP). The dashboard will allow users to register and log in, and once authenticated, they can create products (saved in a MySQL database), view a list of all products, and see a list of all registered users. We’ll cover best practices such as input validation, password hashing, and using prepared statements to prevent SQL injection. Finally, we’ll survey five popular Bootstrap admin templates (with pros and cons) and answer common FAQs. All code examples are tested on a local XAMPP (Apache+MySQL) setup on localhost.
PHP–Bootstrap integration means combining PHP server-side logic and database connectivity with Bootstrap front-end HTML/CSS framework to build dynamic web pages with a modern, responsive UI. In practice, this means we use PHP scripts (e.g., with mysqli or PDO) to handle form data, sessions, and database queries, while using Bootstrap classes and components to style the pages and forms. By linking Bootstrap CSS/JS via CDN or local files into our PHP pages, we can make our PHP-generated pages look polished and mobile-friendly. For example, we might write a PHP file that outputs a Bootstrap-styled form and uses PHP $_POST to handle submissions. According to one tutorial, integrating PHP with Bootstrap, “each component of a unique application can be modified in terms of functionality,” using an MVC-like design, enabling rapid development of visually pleasing interfaces. In summary, PHP generates the dynamic data, and Bootstrap provides a clean, responsive front-end for it.
Using Bootstrap with PHP offers many advantages for developers:
By combining PHP and Bootstrap, we get the best of both worlds: a powerful back-end and a user-friendly, responsive front-end. The table below summarizes key benefits:
Before coding, ensure you have the following set up:
Also, be familiar with:
One example requirement list (from a tutorial) suggests PHP 8.0+, MariaDB 10.x, Bootstrap 5.0+, and a local development environment like XAMPP. We’ll assume XAMPP with MySQL is installed and running.
First, start XAMPP and launch phpMyAdmin (usually at http://localhost/phpmyadmin). Create a new database, for example, admin_panel. Then create two tables: one for users and one for products. You can do this via the SQL tab in phpMyAdmin by running queries like:
-- Create users table
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create products table
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(100) NOT NULL,
product_price DECIMAL(10,2) NOT NULL,
product_cat VARCHAR(100) NOT NULL,
product_details TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
These commands create a users table (with fields username, email, password, etc.) and a products table (with fields product_name, product_price, etc.). In fact, many tutorials use a very similar schema. For example, one guide SQL shows creating a users table with an id, username, email, password, and timestamps, and a products table with product_name, product_price, product_cat, and product_details. Make sure to adjust the database name and table names in your code to match what you create.
It’s best practice to isolate the database connection in a separate PHP file (e.g., db_connect.php) that we can include in all pages. For example, create db_connect.php with the following code:
<?php
// db_connect.php
$servername = "localhost";
$db_username = "root";
$db_password = ""; // XAMPP default has empty password for root
$dbname = "admin_panel";
// Create connection
$db = new mysqli($servername, $db_username, $db_password, $dbname);
// Check connection
if ($db->connect_error) {
die("Database connection failed: " . $db->connect_error);
}
$db->set_charset("utf8"); // ensure proper encoding
?>
This file uses mysqli to connect to MySQL and stores the connection in $db. Adjust $db_username and $db_password if your MySQL has a password. We set the charset to UTF-8 for proper text handling. On each page that needs database access, include this file at the top: include(‘db_connect.php‘);.
Let’s build a registration page (register.php). This will present an HTML form styled with Bootstrap classes, and then handle the POST submission. Here’s an example of the HTML form using Bootstrap 5:
<!-- register.php -->
<?php include('db_connect.php'); session_start(); ?>
<!DOCTYPE html>
<html>
<head>
<title>User Registration</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<h2>Register</h2>
<form action="register.php" method="post">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="pass1" class="form-label">Password</label>
<input type="password" class="form-control" id="pass1" name="password" required>
</div>
<div class="mb-3">
<label for="pass2" class="form-label">Confirm Password</label>
<input type="password" class="form-control" id="pass2" name="confirm_password" required>
</div>
<button type="submit" class="btn btn-primary">Register</button>
<p class="mt-3">Already have an account? <a href="login.php">Log in here</a>.</p>
</form>
</div>

This form uses Bootstrap classes (form-control, mb-3, etc.) for styling. Note the action=”register.php”; we will process the form on the same page. Each field has a name attribute (username, email, password, confirm_password) that PHP will use to read the input.
Below the form, we will add PHP code in the same file to handle the POST request. The logic is as follows:
<?php
// register.php (continuation)
// Initialize an errors array
$errors = [];
// Handle form submission
if ($_SERVER["REQUEST_METHOD"] === "POST") {
// Sanitize and retrieve inputs
$username = trim($_POST['username']);
$email = trim($_POST['email']);
$pass1 = $_POST['password'];
$pass2 = $_POST['confirm_password'];
// Basic validation
if (empty($username)) {
$errors[] = "Username is required";
}
if (empty($email)) {
$errors[] = "Email is required";
}
if (empty($pass1)) {
$errors[] = "Password is required";
}
if ($pass1 !== $pass2) {
$errors[] = "Passwords do not match";
}
// If no errors, proceed to insert
if (empty($errors)) {
// Hash the password securely
$hashedPassword = password_hash($pass1, PASSWORD_DEFAULT);
// Check if the username or email already exists
$stmt = $db->prepare("SELECT id FROM users WHERE username=? OR email=? LIMIT 1");
$stmt->bind_param("ss", $username, $email);
$stmt->execute();
$stmt->store_result();
if ($stmt->num_rows === 0) {
// Insert new user record
$stmt->close();
$stmt = $db->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $username, $email, $hashedPassword);
$stmt->execute();
$stmt->close();
// Registration successful - redirect to login
header("Location: login.php");
exit();
} else {
// Username or email taken
$errors[] = "Username or email already exists";
}
}
}
// Display errors (if any)
if (!empty($errors)) {
echo '<div class="alert alert-danger"><ul>';
foreach ($errors as $e) {
echo "<li>" . htmlspecialchars($e) . "</li>";
}
echo '</ul></div>';
}
?>
Key points in this code:
This completes the registration logic. Notice how we wrap output like error messages with htmlspecialchars() when displaying them, to avoid any HTML injection.
Next, we build the login page. Create a new file, login.php, and start with a Bootstrap-styled form:
<!-- login.php -->
<?php include('db_connect.php'); session_start(); ?>
<!DOCTYPE html>
<html>
<head>
<title>User Login</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<h2>Login</h2>
<form action="login.php" method="post">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">Log In</button>
<p class="mt-3">Don't have an account? <a href="register.php">Register here</a>.</p>
</form>
</div>

This is a simple form with fields for username and password. Now add PHP logic at the top (below the includes) to handle submission:
<?php
// login.php (continuation)
$errors = [];
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$username = trim($_POST['username']);
$password = $_POST['password'];
// Validate input
if (empty($username) || empty($password)) {
$errors[] = "Username and password are required";
} else {
// Fetch user from database
$stmt = $db->prepare("SELECT id, password FROM users WHERE username = ? LIMIT 1");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->store_result();
// Check if user exists
if ($stmt->num_rows === 1) {
$stmt->bind_result($user_id, $hashedPassword);
$stmt->fetch();
$stmt->close();
// Verify the password against the hash
if (password_verify($password, $hashedPassword)) {
// Password is correct
$_SESSION['username'] = $username;
header("Location: dashboard.php"); // redirect to dashboard after login
exit();
} else {
$errors[] = "Incorrect username or password";
}
} else {
$stmt->close();
$errors[] = "Incorrect username or password";
}
}
}
// Display errors
if (!empty($errors)) {
echo '<div class="alert alert-danger"><ul>';
foreach ($errors as $e) {
echo "<li>" . htmlspecialchars($e) . "</li>";
}
echo '</ul></div>';
}
?>
Important details:
Create auth_check.php and include it at the very top of every page that should only be visible to logged-in users:
<?php
// auth_check.php
session_start();
if (!isset($_SESSION['username'])) {
header('Location: login.php'); // bounce guests to the login page
exit();
}
?>
Why? PHP sessions store the user login state server-side; checking the session on each request prevents anyone from bypassing your login form simply by typing a URL.
A single Bootstrap 5 navbar keeps navigation consistent across dashboard pages:
<?php
// navbar.php
if (session_status() === PHP_SESSION_NONE) {
session_start(); // safe: runs only if no session yet
}
?>
<!-- navbar.php -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="dashboard.php">MyAdmin</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarsExample" aria-controls="navbarsExample"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExample">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="nav-link" href="add_product.php">Create Product</a></li>
<li class="nav-item"><a class="nav-link" href="view_products.php">All Products</a></li>
<li class="nav-item"><a class="nav-link" href="view_users.php">All Users</a></li>
</ul>
<span class="navbar-text text-white me-3">Hi, <?=htmlspecialchars($_SESSION['username'])?></span>
<a class="btn btn-outline-light btn-sm" href="logout.php">Logout</a>
</div>
</div>
</nav>
<?php include 'auth_check.php'; ?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dashboard • MyAdmin</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<?php include 'navbar.php'; ?>
<div class="container py-5">
<h1 class="display-6 mb-4">Welcome Back 👋</h1>
<div class="row g-4">
<div class="col-md-4">
<a class="text-decoration-none" href="add_product.php">
<div class="card shadow-sm h-100">
<div class="card-body text-center">
<h2 class="card-title fs-4 mb-2">Add Product</h2>
<p class="card-text">Create a new product record.</p>
</div>
</div>
</a>
</div>
<div class="col-md-4">
<a class="text-decoration-none" href="view_products.php">
<div class="card shadow-sm h-100">
<div class="card-body text-center">
<h2 class="card-title fs-4 mb-2">Product List</h2>
<p class="card-text">Browse / search existing products.</p>
</div>
</div>
</a>
</div>
<div class="col-md-4">
<a class="text-decoration-none" href="view_users.php">
<div class="card shadow-sm h-100">
<div class="card-body text-center">
<h2 class="card-title fs-4 mb-2">User List</h2>
<p class="card-text">View registered users.</p>
</div>
</div>
</a>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

<?php include 'auth_check.php'; include 'db_connect.php'; ?>
<?php
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name']);
$price = trim($_POST['price']);
$cat = trim($_POST['category']);
$details = trim($_POST['details']);
if ($name === '' || $price === '' || $cat === '') {
$errors[] = 'Name, price and category are mandatory.';
} elseif (!filter_var($price, FILTER_VALIDATE_FLOAT)) {
$errors[] = 'Price must be numeric.';
}
if (!$errors) {
$stmt = $db->prepare(
"INSERT INTO products (product_name, product_price, product_cat, product_details)
VALUES (?,?,?,?)"
);
$stmt->bind_param("sdss", $name, $price, $cat, $details); // s = string, d = double
$stmt->execute();
$stmt->close();
header('Location: view_products.php?added=1');
exit();
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Add Product</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<?php include 'navbar.php'; ?>
<div class="container py-4">
<h2>Add Product</h2>
<?php if ($errors): ?>
<div class="alert alert-danger"><ul>
<?php foreach ($errors as $e) echo '<li>'.htmlspecialchars($e).'</li>'; ?>
</ul></div>
<?php endif; ?>
<form method="post">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Product Name</label>
<input class="form-control" name="name" required>
</div>
<div class="col-md-3">
<label class="form-label">Price ($)</label>
<input class="form-control" name="price" type="number" step="0.01" required>
</div>
<div class="col-md-3">
<label class="form-label">Category</label>
<input class="form-control" name="category" required>
</div>
<div class="col-12">
<label class="form-label">Details</label>
<textarea class="form-control" rows="3" name="details"></textarea>
</div>
<div class="col-12">
<button class="btn btn-success">Save Product</button>
</div>
</div>
</form>
</div>
</body>
</html>

Security notes
<?php include 'auth_check.php'; include 'db_connect.php'; ?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>All Products</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<?php include 'navbar.php'; ?>
<div class="container py-4">
<h2 class="mb-3">All Products</h2>
<?php if (isset($_GET['added'])): ?>
<div class="alert alert-success">Product saved successfully.</div>
<?php endif; ?>
<div class="table-responsive">
<table class="table table-striped table-bordered align-middle">
<thead class="table-dark">
<tr>
<th>ID</th><th>Name</th><th>Price ($)</th><th>Category</th><th>Created At</th>
</tr>
</thead>
<tbody>
<?php
$res = $db->query("SELECT * FROM products ORDER BY id DESC");
while ($row = $res->fetch_assoc()):
?>
<tr>
<td><?=$row['id']?></td>
<td><?=htmlspecialchars($row['product_name'])?></td>
<td><?=number_format($row['product_price'],2)?></td>
<td><?=htmlspecialchars($row['product_cat'])?></td>
<td><?=$row['created_at']?></td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
</div>
</body>
</html>

You can add Edit / Delete buttons later, but this read-only list is enough for a first-pass MVP.
<?php include 'auth_check.php'; include 'db_connect.php'; ?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>All Users</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<?php include 'navbar.php'; ?>
<div class="container py-4">
<h2 class="mb-3">Registered Users</h2>
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead class="table-dark">
<tr><th>ID</th><th>Username</th><th>Email</th><th>Registered</th></tr>
</thead>
<tbody>
<?php
$result = $db->query("SELECT id, username, email, created_at FROM users ORDER BY id DESC");
while ($u = $result->fetch_assoc()):
?>
<tr>
<td><?=$u['id']?></td>
<td><?=htmlspecialchars($u['username'])?></td>
<td><?=htmlspecialchars($u['email'])?></td>
<td><?=$u['created_at']?></td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
</div>
</div>
</body>
</html>

<?php
session_start();
session_unset();
session_destroy();
header('Location: login.php?logged_out=1');
exit();
| Template | Key Features | Pros | Cons |
|---|---|---|---|
| AdminLTE 4 | MIT-licensed, Bootstrap 5, dark/light modes, Gulp/Webpack build, 300+ UI components, many 3-rd-party plugins. | • Huge community & docs • Ready-made widgets & pages • Free & actively maintained | • Large payload if you include every plugin • Deep SCSS structure may be overkill for tiny projects |
| SB Admin 2 | Clean Bootstrap 5 starter, minimal styling, Chart.js & DataTables demos, Gulp workflow. | • Lightweight; easy to strip down • Great for teaching newcomers • Active GitHub issues | • Fewer built-in components than bigger kits • Lacks dark/RTL without manual work |
| CoreUI | Enterprise-grade UI library, 100+ components, multiple frameworks (Bootstrap, React, Vue, Angular). | • Consistent API across frameworks • No jQuery • Pro version upgrade path | • A bit heavy out-of-the-box • Some advanced widgets are Pro-only |
| Material Dashboard 3 | Google Material Design 2 look-and-feel, 70+ elements, MIT license, charts, SASS. | • Striking visual style • Free & open-source • Multiple colour schemes | • Material aesthetic isn’t for every brand • Layout variations limited in free build |
| Argon Dashboard 3 | Modern “soft UI” look, fully coded components, SCSS, Bootstrap 5, MIT license. | • Elegant cards/shadows out of the box • Simple file structure • Active updates from Creative Tim | • Fewer starter chart/page examples • Uses custom utility classes, may clash with existing design tokens |
You now have a secure, Bootstrap 5-styled admin dashboard running on PHP 8, MySQL, and XAMPP:
From here, you can extend with edit/delete actions, file uploads, CSRF tokens, role-based access control, and any JavaScript charting or analytics your project needs. Because you have followed modern PHP security guidelines—sessions, password_hash(), and prepared statements, your codebase is ready to evolve into a production grade system.

Hassan Tahir wrote this article, drawing on his experience to clarify WordPress concepts and enhance developer understanding. Through his work, he aims to help both beginners and professionals refine their skills and tackle WordPress projects with greater confidence.