Welcome to a tutorial on how to create a simple PHP admin panel. Since you are reading this, I will assume that you are interested in “powering up” your existing project and want to build an administrative component to it. So here is a sharing of my own – All done in pure HTML, CSS, Javascript, and PHP. Zero third-party frameworks. Read on!
ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.
TABLE OF CONTENTS
DOWNLOAD & NOTES
First, here is the download link to the source code as promised.
QUICK NOTES
For you guys who just want to quickstart and work on your own:
- Create a database and import
lib/SQL-users.sql
. - Open
lib/Core.php
, change all the settings marked withCHANGE TO YOUR OWN
. - Make sure that the PDO extension is enabled in
php.ini
. - That’s all. Accessing
index.php
should redirect to the login page. The default user is john@doe.com, and the password is 123456.
EXAMPLE CODE DOWNLOAD
Click here to download the source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.
QUICKSTART & WALKTHROUGH
All right, let us now get started with a quick walkthrough and how to customize the admin panel.
FOLDERS
There are only two self-explanatory folders for this project.
lib
Put all your PHP libraries and “important files” in this folder.public
All the public resources – CSS, Javascript, images, videos, etc…
MODIFYING THE HTML TEMPLATE
lib/PAGE-top.php
is the top half of the HTML template.lib/PAGE-bottom.php
is the bottom half.- There is only one stylesheet
public/admin.css
and basicpublic/admin.js
to drive the HTML interface. - As in the introduction, there are no third-party frameworks. Feel free to use a library of your choice – jQuery, React, Angular, Bootstrap, Dojo, etc…
ADD YOUR OWN PAGE
<?php
// (A) LOAD THE CORE
require "lib/Core.php";
// (B) HTML
require PATH_LIB . "PAGE-top.php"; ?>
<h1>MY PAGE</h1>
<p>Do something.</p>
<?php require PATH_LIB . "PAGE-bottom.php"; ?>
- As you already know –
lib/Core.php
contains all the necessary core settings. - Then, just load the HTML templates, sandwich your content in-between.
UPDATE THE SIDEBAR
<!-- (C) SIDE BAR -->
<nav id="page-sidebar">
<a href="<?=URL_HOST?>myPage.php">
<span class="ico">⚗</span> MY PAGE
</a>
</nav>
Finally, update the sidebar and add a link to your page.
ADD OR UPDATE USERS
// (A) PASSWORD HASH
$clear = "mypassword";
$hash = password_hash($clear, PASSWORD_DEFAULT);
// (B) PASSWORD VERIFY
$valid = password_verify($clear, $hash);
The user password is encrypted using password_hash()
and can be verified using password_verify()
. Create your own user library if you want, do your own INSERT
and UPDATE
SQL.
HOW IT WORKS
With that, let us now get into more details of “how it works” – This is for you guys who want to learn a little more and “deep customize” the admin panel.
PART 1) PROTECTING THE LIBRARY FOLDER
Deny from all
For you guys who are on Apache, this one line will prevent people from directly accessing and messing with the library files. For example, accessing http://site.com/lib/LIB-database.php
will show a 403 unauthorized error. But no worries, PHP can still access and work with the library files.
P.S. IIS and NGINX users, do your own homework and protect your own library.
PART 2) USERS DATABASE
-- (A) USERS TABLE
CREATE TABLE `users` (
`user_id` int(11) NOT NULL,
`user_email` varchar(255) NOT NULL,
`user_name` varchar(255) NOT NULL,
`user_password` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `users`
ADD PRIMARY KEY (`user_id`),
ADD UNIQUE KEY `user_email` (`user_email`),
ADD KEY `user_name` (`user_name`);
ALTER TABLE `users`
MODIFY `user_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
-- (B) DEFAULT USER
INSERT INTO `users` (`user_id`, `user_email`, `user_name`, `user_password`) VALUES
(1, 'john@doe.com', 'John Doe', '$2y$10$vZJy7y4uqQQTRN3zdi2RE.5ZJJzGEEPnzEjFXm4nEOx023XQ2Qe..');
Field | Description |
user_id |
Primary key, the user ID. |
user_email |
The user email, set to unique to prevent duplicate registrations. |
user_name |
The user’s name. |
user_password |
The password. |
I initially didn’t want to create a users table, as some of you guys already have an existing one. But this should benefit those who don’t, and the admin panel should work “out of the box” with proper login and logoff mechanism.
PART 3) CORE CONFIG FILE
<?php
// (A) ERROR HANDLING - CHANGE TO YOUR OWN
error_reporting(E_ALL & ~E_NOTICE);
ini_set("display_errors", 1);
// ini_set("log_errors", 1);
// ini_set("error_log", "PATH/error.log");
// (B) DATABASE SETTINGS - CHANGE TO YOUR OWN
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8");
define("DB_USER", "root");
define("DB_PASSWORD", "");
// (C) URL
define("URL_HOST", "http://localhost/"); // CHANGE TO YOUR OWN
define("URL_PUBLIC", URL_HOST . "public/");
// (D) FILE PATHS
define("PATH_LIB", __DIR__ . DIRECTORY_SEPARATOR);
define("PATH_BASE", dirname(PATH_LIB) . DIRECTORY_SEPARATOR);
// (E) START SESSION
session_start();
Next, we have a simple core config file – To store all the common system settings and start the session.
PART 4) DATABASE LIBRARY
<?php
class DB {
// (A) CONSTRUCTOR - CONNECT TO THE DATABASE
private $pdo = null;
private $stmt = null;
public $error;
function __construct () {
try {
$this->pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
} catch (Exception $ex) { exit($ex->getMessage()); }
}
// (B) DESTRUCTOR - CLOSE DATABSE CONNECTION
function __destruct () {
if ($this->stmt !== null) { $this->stmt = null; }
if ($this->pdo !== null) { $this->pdo = null; }
}
// (C) EXECUTE SQL QUERY
function exec ($sql, $data=null) {
try {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($data);
return true;
} catch (Exception $ex) {
$this->error = $ex->getMessage();
return false;
}
}
// (D) FETCH
function fetch ($sql, $data=null) {
if ($this->exec($sql, $data) === false) { return false; }
return $this->stmt->fetch();
}
}
This simple PDO database library is another small piece to help you guys develop a little faster – Go ahead and adopt it, or delete and use your own library.
- (A & B) When a
$DB = new DB()
is created, the constructor will connect to the database. The destructor closes the connection. - (C)
exec()
Executes an SQL query. - (D)
fetch()
Runs aSELECT
SQL query.
PART 5) USER SESSION AJAX HANDLER
<?php
require "lib/Core.php";
if (isset($_POST["req"])) { switch ($_POST["req"]) {
// (A) INVALID REQUEST
default: exit("INVALID REQUEST");
// (B) LOGIN
case "in":
// (B1) ALREADY SIGNED IN
if (isset($_SESSION["user"])) { exit("OK"); }
// (B2) VERIFY
else {
require PATH_LIB . "LIB-database.php";
$DB = new DB();
$user = $DB->fetch(
"SELECT * FROM `users` WHERE `user_email`=?", [$_POST["email"]]
);
$pass = is_array($user);
if ($pass) { $pass = password_verify($_POST["password"], $user["user_password"]); }
if ($pass) {
$_SESSION["user"] = [];
foreach ($user as $k=>$v) { if ($k!="user_password") {
$_SESSION["user"][$k] = $v;
}}
exit("OK");
}
exit("Invalid user/password");
}
// (C) LOGOUT
case "out":
unset($_SESSION["user"]);
exit("OK");
}}
This AJAX handler deals with the user login and logout. Very simply:
- (B) To request for login, we send
$_POST = ["req"=>"in", "email"=>"EMAIL", "password"=>"PASS"]
to this script. - (C) To request a logout, we send
$_POST = ["req"=>"out"]
.
Yep, that’s all. You may want to build your own user library, and further clean up the login sequence.
PART 6) HTML TEMPLATE
<?php
// (A) LOGIN CHECK
$_ISADMIN = isset($_SESSION["user"]);
if (!$_ISADMIN && !isset($_ISLOGIN)) {
header("Location: ".URL_HOST."login.php"); exit();
}
if ($_ISADMIN && isset($_ISLOGIN)) {
header("Location: ".URL_HOST); exit();
} ?>
<!DOCTYPE html>
<html>
<head>
<title>ADMIN PANEL</title>
<meta name="robots" content="noindex, nofollow">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=2">
<link href="<?=URL_PUBLIC?>admin.css" rel="stylesheet">
<script>var url = {host:"<?=URL_HOST?>",public:"<?=URL_PUBLIC?>"};</script>
<script async src="<?=URL_PUBLIC?>admin.js"></script>
</head>
<body>
<!-- (B) NOW LOADING SPINNER -->
<div id="page-loader">
<img src="<?=URL_PUBLIC?>cube-loader.svg"/>
</div>
<?php if ($_ISADMIN) { ?>
<!-- (C) SIDE BAR -->
<nav id="page-sidebar">
<a href="<?=URL_HOST?>pageA.php">
<span class="ico">⚗</span> Module A
</a>
<a href="<?=URL_HOST?>pageB.php">
<span class="ico">⚘</span> Module B
</a>
</nav>
<?php } ?>
<!-- (D) MAIN CONTENTS -->
<div id="page-main">
<?php if ($_ISADMIN) { ?>
<!-- (D1) NAVIGATION BAR -->
<nav id="page-nav">
<div id="page-button-side" onclick="adm.side();">☰</div>
<div id="page-button-out" onclick="adm.bye();">☓</div>
</nav>
<?php } ?>
<!-- (D2) PAGE CONTENTS -->
<main id="page-contents">
</main>
</div>
</body>
</html>
This looks complicated at first, but look closely. There are only a few main sections:
<nav id="page-sidebar">
Sidebar to the left. Hides itself on small screens.<div id="page-main">
The main area is to the right.<nav id="page-nav">
Top navigation bar (inside the main area).<div id="page-button-side">
This button is used to toggle show/hide the sidebar on small screens.<div id="page-button-out">
The logout button.
<main id="page-contents">
Actual contents (inside the main area)
That’s about it for the template, the only other interesting thing is the small block of PHP code at the top – It detects if the user is signed in, and redirects to the login page if not. Feel free to change this if you intend to use a different login URL.
PART 7) THE JAVASCRIPT
var admin = {
// (A) SHOW/HIDE "NOW LOADING" BLOCK
loading : (show) => {
var block = document.getElementById("page-loader");
if (show) { block.classList.add("active"); }
else { block.classList.remove("active"); }
},
// (B) TOGGLE SIDE BAR
sidebar : () => {
document.getElementById("page-sidebar").classList.toggle("active");
},
// (C) SIGN OFF
bye : () => { if (confirm("Sign off?")) {
admin.ajax({
url : url.host + "ajax-session.php",
data : { req : "out" },
ok : () => { location.reload(); }
});
}},
// (D) AJAX FETCH
// url : target url
// data : data to send
// ok : function to run on server "OK"
ajax : (opt) => {
// (D1) FORM DATA
let data = new FormData();
for (let [k, v] of Object.entries(opt.data)) { data.append(k, v); }
// (D2) FETCH
fetch(opt.url, { method:"POST", body:data })
.then((res) => {
if (res.status != 200) {
alert(`Server ${res.status} error`);
console.error(res);
} else { return res.text(); }
})
.then((txt) => {
if (txt != "OK") { alert(txt); }
else { opt.ok(); }
})
.catch((err) => { console.error(err); });
}
};
Function | Description |
loading() |
Show/hide the “now loading” spinner. |
sidebar() |
Toggle show/hide the sidebar. |
bye() |
Sign off the user. |
ajax() |
AJAX fetch. |
PART 8) LOGIN PAGE
<?php
// (A) INIT
require "lib/Core.php";
$_ISLOGIN = 1;
require PATH_LIB . "PAGE-top.php"; ?>
<!-- (B) JAVASCRIPT -->
<script>
function login () {
admin.ajax({
url : url.host + "ajax-session.php",
data : {
req : "in",
email : document.getElementById("user_email").value,
password : document.getElementById("user_password").value
},
ok : () => { location.href = "index.php"; }
});
return false;
}
</script>
<!-- (C) CSS -->
<style>
#login-form {
max-width: 320px; margin: 0 auto; padding: 20px;
background: #eee; border: 1px solid #ccc;
}
#login-form input { width: 100%; margin-top: 20px; }
</style>
<!-- (D) LOGIN FORM -->
<form id="login-form" onsubmit="return login();">
<h1>LOGIN</h1>
<input type="email" placeholder="Email" id="user_email" required />
<input type="password" placeholder="Password" id="user_password" required />
<input type="submit" value="Sign In"/>
</form>
<?php require PATH_LIB . "PAGE-bottom.php"; ?>
Just a “regular AJAX login form”.
PART 9) MAIN PAGE
<?php
require "lib/Core.php";
require PATH_LIB . "PAGE-top.php";?>
YOUR DASHBOARD HERE
<?php require PATH_LIB . "PAGE-bottom.php"; ?>
The main page is nothing but an empty shell – Feel free to create your own dashboard.
USEFUL BITS & LINKS
That’s all for the tutorial, and here are a few small extras and links that may be useful to you.
LINKS & REFERENCES
- CSRF Token Protection – Code Boxx
- PHP User Role Management – Code Boxx
- PHP Encrypt Decrypt Verify Password – Code Boxx
THE END
Thank you for reading, and we have come to the end of this tutorial. I hope that it has helped speed up your project development, and if you have anything to share with this guide, please feel free to comment below. Good luck and happy coding!
Could you possibly help.
What should I change in line10?
Struggling. Very grateful for all your hard work.
login.php line10
https://code-boxx.com/how-to-debug-javascript/
https://code-boxx.com/how-to-debug-php-code/
https://code-boxx.com/simple-ajax-php/
https://code-boxx.com/relative-absolute-url-base-tag/
https://code-boxx.com/faq/#help