Build your own CMS with PHP and MySQL from scratch step by step Part-1

Learning a CMS is the start of professional web development. If you have already used some CMSs such as Wordpress, Drupal, Joomal etc then you will already know what a CMS is, how they look and how they work. However, even if you have not used any of those CMSs before you do not need to worry as we will not be using elements of those systems.

What is a CMS?

CMS stands for Content Management System. It is a way to manage content (your data) on a server. Using a CMS makes your code modular, with web pages created dynamically. A CMS makes code much more sensible and readable. For example, in a dynamic web page, we have a header (the top section of the page), sidebars, a footer (bottom section), content from a database (most things are stored in the database except files) and some functions & classes for handling the web page (like manipulating database, form validations, security handler, etc.). All of these sections are placed in different files and combined in order to generate a web page. For more read CMS vs MVC frameworks.


  • For this application you must have a basic knowledge of HTML, CSS and PHP. I will also use very basic jquery. If you don't know anything about jquery then you will still be able to understand this series.
  • An install of LAMP, WAMP or XAMPP according to your operating system.
  • PHP 5.4+
  • Bootstrap ( we will download it later, you need not to know anything about it.It is a pre-written collection of css and js files which provide a liquid layout and essential theme. It will reduce our time so that we can put our focus on PHP).

If you are very new with php then use my step by step php tutorials Learn PHP

Why use this series?

This series is only for learning purpose so I am keeping it quite simple. It is far away from professional level but not so far that I cannot cover common issues. It is for people who can build a static or dynamic website but at a very low level.

In this series I will make a To Do Application with the following functions:

  • Log in, Log out and Register users
  • Create a to do entry under different labels
  • Save a due date and calculate remaining time
  • Show the progress of the work
  • Edit and delete any entry
  • Admin Section for handling themes and users

The CMS will has following qualities:

  • Portability: Just like wordpress, it can have custom themes and widgets.
  • Perfect distribution: It will be very modular with different files and folders.
  • Secure: Here we are building a very small application but we will consider all types of security issues for best developing practice.
  • Admin interface: In our case, admin interface is just managing themes and users, but you can extend it by adding more functions.
  • Pretty links (Routing): Just like an MVC framework, this CMS has a routing system.

The code is open source and available in my github repository. If you don't want to copy the code from here then you can download a zip file from there. Live demo is at TODO LIVE DEMO. Admin section is at ADMIN SECTION.

What can you do after learning from this series?

  1. Make better use of resources using less repetitive code
  2. Creating a dynamic website
  3. Distribute code in files and folders.
  4. Security using CSRF token, escaping special parameters, regex matching (preg_match function) and mysql prepare statements.
  5. Create routing
  6. Use bootstrap
  7. More functions of PHP

Screen shot of To DO dashboard

Weakness of these tutorials:

NOTE: After learning from these tutorials, you can resolve these weaknesses yourself.

  • I am not focusing on UI because this series of tutorials are about PHP and Mysql.
  • I am creating a very simple application (To do app) which has limited features.
  • I will not cover relational database queries because our application does not require it. But this CMS is portable and you can add these functions yourself.
  • I am also not covering browser compatibility issues.
  • I am not focusing on SEO. You should consider adding these functions yourself.

NOTE: You are welcome to contribute to these tutorials.

Now let's start.

Create a directory and files as shown here in folder www or htdoc according to your operating system.

My file structure is following.

todolist directory

Take a good look at this structure. In the root folder todolist, there are six folders and one file.

Don't worry if you do not understand any point. We will discuss in further detail later.

  • todolist is my root folder (root folder of my application).
  • There is a .htaccess file in root folder (todolist folder). This file is redirecting all requests from browser to index.php file (in todolist folder).
  • All configurations are defined in config.php file.
  • index.php is the file which receives all requests and generates dynamic web pages as a response.
  • The includes folder is the base folder, i.e., all base files are present in the includes folder. All other files extend the functions of these files. Base.php is the base file of the all files. This file will be extended by Application.php, Widget.php and TemplateFunctions.php.
  • The application folder contains all of the application files, i.e., the files which are written for any application. In our case, Todo is a application. Every application extends Application.php.
  • The libraries folder contains the library files (general required files). For example, database related files.
  • The templates folder contains the themes and static files.Template files use the functions defined in TemplateFunction.php.
  • The widget folder has widget modules.All widgets will extend Widget.php. In this folder we will write our widgets.
  • The general static files which are independent of any theme are stored in asset folder.

Ignore the .gitignore,, LICENSE, cms_todo.sql and config.example.php files. These files are for sending code on github.

How does this CMS work?

As mentioned before, the .htaccess file will redirect all requests to index.php file in the root folder. First of all the index.php file will define the security variable which will be checked by each file so that no-one can access any other file directly. In other words, all other files (except static files) can be opened only by the index.php file.

Then configurations from the config.php file are fetched and stored in constants so that other files can use them. After that we define a session variable with the name CSRF. This variable will be verified when we submit any form. It ensures that the form submission request is coming from a web page which is served by our website.

After this we call the TemplateFunction class from the TemplateFunction.php file. TemplateFunction registers the theme and includes index.php file from the theme folder (theme folders are in the templates folder). So how are web pages generated in the index.php file inside themes? We will learn this in the series.

Prepare the .htaccess file

The .htaccess file is used by the server. It provides a way to make configuration changes on a per-directory basis. The requests which are passed to todolist directory will be filtered by this file (as it is placed in todolist folder). The code of this file is:

  1. RewriteEngine on
  2. RewriteCond %{REQUEST_FILENAME} !-d
  3. RewriteCond %{REQUEST_FILENAME} !-f
  4. RewriteCond $1 !^(images|photos|css|js|robots\.txt)
  5. RewriteRule ^(.+)$ index.php/$1 [NC,L]

NOTE: This file only work when rewrite mode is turn on.

The first line is telling us that rewrite mode is turned on. The second and third lines are collecting all requests for files or directories. The fourth line removes those requests which are static requests. The fifth line is sending all other requests to the index.php file. For more detail on the .htaccess file read official document of Apache.

Set configurations in config.php file

Now write these configurations:

  1. <?php
  2. return [
  3. // set a database connection as default
  4. // assign the name defined in connection variable
  5. 'database' => 'mysql', // mysql is connection name, can be anything
  6. // config database connections
  7. 'connection' => [
  8. // name of this connection, used in above option only
  9. 'mysql' => [
  10. 'driver' => 'mysql', //database type
  11. 'host' => 'your host', //database host name, on local server it is 'localhost'
  12. 'database' => 'your database name', // database name
  13. 'username' => 'your username', // database username
  14. 'password' => 'your password', // user password
  15. ],
  16. ],
  17. // set the location of application withour trailing slash
  18. // in my case it is 'http://localhost/todolist'
  19. 'root' => '', // example 'http://localhost/todolist' or
  20. // register apps which are defined in application folder
  21. 'apps' => [
  22. 'auth',
  23. 'todo',
  24. 'admin',
  25. ],
  26. // secret string for encreption
  27. 'secret' => 'this_is_secret_string', // example 'fa&k+j@sdf!has^dh-iu!d#dh$sd'
  28. ];

This code is well commented. Why is ‘connection’, an array of connections defined instead of using one connection? It is a port for multiple database connections. I am using it because I am using the same code in both local and production servers. I only have to change the value of ‘database’ to correct the connection. We will register our application as ‘apps’ . Here we are defining three applications:

  • auth: It will handle the authentication (log in, log out, registration, reset password).
  • todo: This is our application :)
  • admin: This is our admin section.

We will create these apps in future tutorials. Note that this file has no closing tag for php (?>). This is to remove extra white space as the PHP tag does not need to be closed.

Create index.php in root:

Now we will handle requests using index.php. First close displaying error messages and define a security variable:

  1. <?php
  2. if (ini_get('display_errors')) {
  3. ini_set('display_errors', '0');
  4. }
  5. $security_check = 1;

Lines 3-5 will stop displaying errors. In development (on your local server), it is good to stp displaying errors so that you can track the debug. So, in the local server change the above code with:

  1. <?php
  2. if (!ini_get('display_errors')) {
  3. ini_set('display_errors', '1');
  4. }
  5. $security_check = 1;

$security_check is a variable which we will check in our files.

Include config.php

Now add this code in index.php file:

  1. // define configurations
  2. $config = require_once('config.php');
  3. // define root folder
  4. define('ROOT', $config['root']);
  5. // define database
  6. $database_config = $config['connection'][$config['database']];
  7. $driver = $database_config['driver'];
  8. define('DATABASE', ucfirst($driver).'Database');
  9. define('DATABASE_NAME', $database_config['database']);
  10. define('DATABASE_USER', $database_config['username']);
  11. define('DATABASE_PASS', $database_config['password']);
  12. define('DATABASE_SERVER', $database_config['host']);
  13. // register apps
  14. $GLOBALS['APPS'] = $config['apps'];
  15. // define production secret key
  16. define('SECRET', $config['secret']);

In line 1, we are including config.php which returns all configurations as an array. In line 4, we are defining a constant named ROOT . In line 9-14, we are defining constants for the database.

Add CSRF token:

Now add this code for adding CSRF.

  1. session_start();
  2. if(isset($_SESSION['CSRF'])!== true)
  3. {
  4. // all character which can be in random string
  5. $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  6. $charactersLength = strlen($characters);
  7. $randomString = '';
  8. for ($i = 0; $i < 32; $i++) {
  9. // choosing one character from all characters and adding it to random string
  10. $randomString .= $characters[rand(0, $charactersLength - 1)];
  11. }
  12. // store generated csrf token using sha512 hashing
  13. $_SESSION['CSRF'] = hash('sha512',time().''.$randomString);
  14. }
  15. // make CSRF as constant for using in templates
  16. define('CSRF', $_SESSION['CSRF']);
  17. // if request is not GET then verify csrf_token
  18. if($_SERVER['REQUEST_METHOD']!=='GET')
  19. {
  20. if(!isset($_REQUEST['csrf_token']) || $_REQUEST['csrf_token'] !== $_SESSION['CSRF'])
  21. {
  22. // if wrong csrf_token stop user from accessing
  23. header('HTTP/1.1 403 Forbidden');
  24. exit;
  25. }
  26. }

We are starting a session for the user (if user has already a session the old session will continue). Then we are checking if the user session has a variable with name CSRF. If not, then we are generating a 32 character long random string and hashing this string with time using the hash() function. After this we are storing the token in a constant. Lines 21-29 are for verifying requests in form submission cases. Up to this point our index.php file is:

  1. <?php
  2. if (ini_get('display_errors')) {
  3. ini_set('display_errors', '0');
  4. }
  5. $security_check = 1;
  6. // define configurations
  7. $config = require_once('config.php');
  8. // define root folder
  9. define('ROOT', $config['root']);
  10. // define database
  11. $database_config = $config['connection'][$config['database']];
  12. $driver = $database_config['driver'];
  13. define('DATABASE', ucfirst($driver).'Database');
  14. define('DATABASE_NAME', $database_config['database']);
  15. define('DATABASE_USER', $database_config['username']);
  16. define('DATABASE_PASS', $database_config['password']);
  17. define('DATABASE_SERVER', $database_config['host']);
  18. // register apps
  19. $GLOBALS['APPS'] = $config['apps'];
  20. // define production secret key
  21. define('SECRET', $config['secret']);
  22. session_start();
  23. if(isset($_SESSION['CSRF'])!== true)
  24. {
  25. $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  26. $charactersLength = strlen($characters);
  27. $randomString = '';
  28. for ($i = 0; $i < 32; $i++) {
  29. $randomString .= $characters[rand(0, $charactersLength - 1)];
  30. }
  31. $_SESSION['CSRF'] = hash('sha512',time().''.$randomString);
  32. }
  33. define('CSRF', $_SESSION['CSRF']);
  34. if($_SERVER['REQUEST_METHOD']!=='GET')
  35. {
  36. if(!isset($_REQUEST['csrf_token']) || $_REQUEST['csrf_token'] !== $_SESSION['CSRF'])
  37. {
  38. header('HTTP/1.1 403 Forbidden');
  39. exit;
  40. }
  41. }

Later we will add TemplateFunction class to it for generating dynamic web pages.

Next tutorial

About Harish Kumar

Harish is an interested person in the field of web development and blogging. He works for the need of young web developers in learning various languages, latest technologies and other essential tips and tricks. If you need some help or you have some suggestion then you email him at without any hesitation. You can also suggest/demand for articles of your own choice.

Related Articles

In this part of free tutorial series, I am telling you about syntax, variables, data types, comments, constants and o...
Introduction to PHP
In this part of free step by step tutorial series I will teach you about for loop, while loop and do while loop of PH...
loops in PHP
In this part of free step by step tutorial series I will teach you about if, if else, nested and switch statements of...
Conditional statements in php

Login or Sign up to leave comment.