A Guide to Building Your Own Custom WordPress Plugin

In this tutorial, we will dive into the development of a Custom WordPress Plugin using the PHP language. The primary goal is to provide students and beginners, particularly those new to WordPress and PHP, with a reference to learning useful techniques to enhance their knowledge and programming capabilities. I'll be offering sample snippets and a sample WordPress plugin source code that demonstrates our main goal in this tutorial.

What is WordPress?

WordPress is a widely utilized open-source content management system (CMS) that enables users to create and manage websites and blogs. It is written in PHP and employs a MySQL or MariaDB database. WordPress has evolved from its origins as a blogging platform to a versatile CMS utilized by individuals, businesses, and organizations for various types of websites, including blogs, portfolios, e-commerce sites, and corporate websites.

What is WordPress Plugin?

A WordPress plugin is a software component that extends and enhances the functionality of a WordPress website. Plugins are pivotal in making WordPress a flexible and customizable content management system (CMS). They enable users to incorporate specific features or capabilities into their websites without modifying the core WordPress code.

How to Create a Custom WordPress Plugin?

Step 1

To create a Custom WordPress Plugin, make a new directory in your WordPress website within the `source_code_path>wp-contents>plugins` folder. Then, create a new PHP file.

Step 2

Open your newly created PHP file with your preferred code editor, such as MS VS Code, Sublime, or Notepad++.

Step 3

Next, we need to provide the Plugin's information header at the top line of the PHP file. The information header is enclosed in comment tags. Below is an example information header for a sample WordPress Plugin:

  1. <?php
  2. /**
  3.  * Plugin Name: My Custom Plugin Name
  4.  * Plugin URI: URI of your plugin where your docs, updates, or etc located
  5.  * Description: A brief description of yout plugin
  6.  * Version: your plugin version
  7.  * Author: the plugins author name
  8.  * Author URI: the plugins author URI
  9.  * License: license name e.g. GPL2
  10.  */  
  11. ?>

After completing these steps, you will be able to locate your Custom WordPress Plugin on the Plugins Page of your WordPress admin side.

Custom WordPress Plugin

For a more better understanding of creating a Custom WordPress Plugin, I have developed a simple plugin that includes CRUD (Create, Read, Update, and Delete) features on the admin side. The provided plugin script also involves the creation of a new database table.

Below is the PHP script for the simple plugin I created:

  1. <?php
  2. /**
  3.     * Plugin Name: My First Custom Plugin
  4.     * Plugin URI: https://sample-domain.com
  5.     * Description: Lorem ipsum dolor sit amet consectetur adipisicing elit.
  6.     * Version: 1.0
  7.     * Author: oretnom23
  8.     * Author URI: https://www.sourcecodester.com/user/257130/activity
  9.     * License: GPLv2 or later
  10.     */    
  11.  
  12. if ( ! defined( 'ABSPATH' ) ) {
  13.     die;
  14. }
  15.  
  16. class MyFirstPlugin{
  17.     public $notices = [];
  18.     function __construct()
  19.     {
  20.         add_action( 'admin_menu', [ $this, 'pages' ] );
  21.         add_action( 'admin_enqueue_scripts', [$this, 'bootstrap_scripts'], 10, 1 );
  22.  
  23.         // Process Form
  24.         add_action( 'admin_init', [ $this, 'save_form' ] );
  25.         add_action( 'admin_init', [ $this, 'delete_mfcp' ] );
  26.  
  27.         $this->notices = get_option("mfcp_notices", array());
  28.         // print_r(gettype($this->notices));exit;
  29.         add_action( 'admin_notices', [$this, 'display_notices'] );
  30.     }
  31.  
  32.     function pages(){
  33.         //add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, $position );
  34.         add_menu_page(__('My First Custom Plugin'), __('Custom Plugin'), 'administrator', 'my-first-custom-plugin-page', [$this, 'my_plugin_list_page'], '', 25);
  35.         add_submenu_page('my-first-custom-plugin-page', __('Manage Plugin\'s DB'), __('Add New'), 'administrator', 'my-first-custom-plugin-page-form', [$this, 'my_plugin_form_page']);
  36.     }
  37.  
  38.     function display_notices(){
  39.         // print_r($this->notices);exit;
  40.         if(count($this->notices) > 0){
  41.             foreach($this->notices as $k =>$notice){
  42.                 ?>
  43.                 <div class="notice notice-<?= $notice['type'] ?> <?= ($notice['dismissible']) ? "is-dismissible" : "" ?>">
  44.                     <?= htmlspecialchars_decode($notice['message']) ?>
  45.                 </div>
  46.                 <?php
  47.                 unset($this->notices[$k]);
  48.             }
  49.         }
  50.         update_option("mfcp_notices", []);
  51.  
  52.     }
  53.     function activate(){
  54.         // Install Plugin's Database
  55.         $this->install_db();
  56.     }
  57.     function deactivate(){
  58.        
  59.     }
  60.     function uninstall(){
  61.     }
  62.  
  63.     /**
  64.         * Enqueue Styles and Scripts
  65.         */
  66.     function bootstrap_scripts($hook){
  67.         // Enqueue Bootstrap CDN CSS and JS only in plugin's page
  68.         if($hook == 'toplevel_page_my-first-custom-plugin-page' || $hook == 'custom-plugin_page_my-first-custom-plugin-page-form'){
  69.             wp_enqueue_style("fontawesome-icon-cdn","https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/css/all.min.css",[],"6.1.2", "all");
  70.             wp_enqueue_style("bootstrap-css-cdn","https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css",[],"5.3.2", "all");
  71.  
  72.             wp_enqueue_script("fontawesome-icon-js-cdn","https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.2/js/all.min.js",[],"6.1.2", "all");
  73.             wp_enqueue_script("bootstrap-js-cdn","https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js",[],"5.3.2", "all");
  74.         }
  75.         // echo $hook;exit;
  76.     }
  77.  
  78.     /**
  79.         * Create Plugin's Database
  80.         */
  81.     function install_db(){
  82.         global $wpdb;
  83.         $table = $wpdb->prefix."mfp_tbl";
  84.         $qry = $wpdb->prepare("SHOW TABLES LIKE %s", $wpdb->esc_like($table));
  85.         if($wpdb->get_var($qry) == $table){
  86.             /**
  87.                 * Table Already Exists
  88.                 */
  89.  
  90.         }else{
  91.             /**
  92.                 * Table Doesn't Exists on the Database
  93.                 * - Install Table
  94.                 */
  95.             $charset_collate = $wpdb->get_charset_collate();
  96.  
  97.             $sql = "CREATE TABLE IF NOT EXISTS $table  (
  98.                `id` int(30) NOT NULL PRIMARY KEY AUTO_INCREMENT,
  99.                `meta_field` text DEFAULT NULL,
  100.                `meta_value` text DEFAULT NULL,
  101.                `created_at` datetime NOT NULL DEFAULT current_timestamp(),
  102.                `updated_at` datetime DEFAULT NULL ON UPDATE current_timestamp()
  103.            ) $charset_collate;";
  104.             require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
  105.             dbDelta($sql);
  106.         }
  107.     }
  108.  
  109.     /**
  110.         * Create Plugin's Form Page
  111.         */
  112.     function my_plugin_form_page(){
  113.         global $wpdb;
  114.         if(isset($_GET['mfcp_id'])){
  115.             $data = $wpdb->get_row(
  116.                 $wpdb->prepare("SELECT * FROM `{$wpdb->prefix}mfp_tbl` where `id` = %d", [$_GET['mfcp_id']]),
  117.                 ARRAY_A
  118.             );
  119.             extract($data, EXTR_PREFIX_ALL, "mfcp");
  120.         }
  121.         ?>
  122.         <style>
  123.             .card{
  124.                 max-width: unset !important;
  125.             }
  126.         </style>
  127.         <div class="container-fluid">
  128.             <div class="card shadow col-lg-5 col-md-8 col-sm-12 col-12 mx-auto p-0">
  129.                 <div class="card-header rounded-0">
  130.                     <div class="card-title">Plugin's Sample Form <?= (!empty($mfcp_id ?? "")? " - Updating #{$mfcp_id}" : "") ?></div>
  131.                 </div>
  132.                 <div class="card-body">
  133.                     <div class="container-fluid">
  134.                         <form action="" id="sample-form" method="POST">
  135.                             <?php wp_nonce_field( "mfcp-sample-form" ); ?>
  136.                             <input type="hidden" name="action" value="save-mfcp-sample-form">
  137.                             <input type="hidden" name="id" value="<?= $mfcp_id ?? "" ?>">
  138.                             <div class="mb-3">
  139.                                 <label for="meta_field" class="form-label">Meta Field <small class="text-danger">*</small></label>
  140.                                 <input type="text" class="form-control rounded-0" id="meta_field" name="meta_field" value="<?= $_POST['meta_field'] ?? $mfcp_meta_field ?? "" ?>" required="required" autofocus>
  141.                             </div>
  142.                             <div class="mb-3">
  143.                                 <label for="meta_value" class="form-label">Meta Value <small class="text-danger">*</small></label>
  144.                                 <textarea name="meta_value" id="meta_value" rows="3" class="form-control rounded-0" requried=""><?= $_POST['meta_value'] ?? $mfcp_meta_value ?? "" ?></textarea>
  145.                             </div>
  146.                             <div class="mb-3">
  147.                                 <button class="btn btn-primary rounded-0" type="submit">Save</button>
  148.                             </div>
  149.                         </form>
  150.                     </div>
  151.                 </div>
  152.             </div>
  153.         </div>
  154.         <?
  155.     }
  156.  
  157.     /**
  158.         * Create Plugin's List Page
  159.         */
  160.  
  161.     function my_plugin_list_page(){
  162.         global $wpdb;
  163.         $data = $wpdb->get_results(
  164.             $wpdb->prepare("
  165.                SELECT * FROM `{$wpdb->prefix}mfp_tbl` order by id ASC
  166.            "),
  167.             ARRAY_A
  168.         );
  169.         ?>
  170.         <style>
  171.             .card{
  172.                 max-width: unset !important;
  173.             }
  174.         </style>
  175.         <div class="container-fluid">
  176.             <div class="card shadow col-12 p-0">
  177.                 <div class="card-header rounded-0">
  178.                     <div class="card-title">Plugin's Database Data</div>
  179.                 </div>
  180.                 <div class="card-body">
  181.                     <div class="container-fluid">
  182.                         <div class="table-responsive">
  183.                             <table class="table table-striped table-hover table-bordered">
  184.                                 <colgroup>
  185.                                     <col width="20%">
  186.                                     <col width="30%">
  187.                                     <col width="30%">
  188.                                     <col width="20%">
  189.                                 </colgroup>
  190.                                 <thead>
  191.                                     <tr>
  192.                                         <th class="text-center px-2 py-1 text-light bg-primary">Date Added</th>
  193.                                         <th class="text-center px-2 py-1 text-light bg-primary">Meta Field</th>
  194.                                         <th class="text-center px-2 py-1 text-light bg-primary">Meta Value</th>
  195.                                         <th class="text-center px-2 py-1 text-light bg-primary">Action</th>
  196.                                     </tr>
  197.                                 </thead>
  198.                                 <tbody>
  199.                                     <?php if(!empty($data)): ?>
  200.                                     <?php foreach($data as $row): ?>
  201.                                         <tr>
  202.                                             <td class="px-2 py-1"><?= date("M d, Y g:i A", strtotime($row['created_at'])) ?></td>
  203.                                             <td class="px-2 py-1"><?= $row['meta_field'] ?></td>
  204.                                             <td class="px-2 py-1"><?= $row['meta_value'] ?></td>
  205.                                             <td class="px-2 py-1">
  206.                                                 <div class="input-group d-flex justify-content-center">
  207.                                                     <a href="<?= admin_url("admin.php?page=my-first-custom-plugin-page-form&mfcp_id={$row['id']}") ?>" class="btn btn-sm btn-outline-primary rounded-0" title="Edit"><i class="fa fa-edit"></i></a>
  208.                                                     <a href="<?= admin_url("admin.php?page=my-first-custom-plugin-page-form&action=delete_mfcp&mfcp_id={$row['id']}") ?>" class="btn btn-sm btn-outline-danger rounded-0 delete-mfcp" title="Delete"><i class="fa fa-trash"></i></a>
  209.                                                 </div>
  210.                                             </td>
  211.                                         </tr>
  212.                                     <?php endforeach; ?>
  213.                                     <?php else: ?>
  214.                                         <tr>
  215.                                             <th class="text-center px-2 py-1" colspan="4">No Data Found!</th>
  216.                                         </tr>
  217.                                     <?php endif; ?>
  218.                                 </tbody>
  219.                             </table>
  220.                         </div>
  221.                     </div>
  222.                 </div>
  223.             </div>
  224.         </div>
  225.         <script>
  226.             var delete_mfcp = document.querySelectorAll('.delete-mfcp')
  227.             delete_mfcp.forEach(el=>{
  228.                 el.addEventListener('click', function(e){
  229.                     if(confirm(`Are you sure to delete this data?`) === false)
  230.                     e.preventDefault();
  231.                 })
  232.             })
  233.         </script>
  234.         <?
  235.     }
  236.    
  237.  
  238.     /**
  239.         * Save Form Data
  240.         */
  241.     function save_form(){
  242.         global $wpdb;
  243.         extract($_POST, EXTR_PREFIX_ALL, "mfcp");
  244.         $resp=[];
  245.         if(
  246.             isset($_POST['action']) &&
  247.             $_POST['action'] == 'save-mfcp-sample-form'
  248.         ){
  249.             if(!wp_verify_nonce( $mfcp__wpnonce, 'mfcp-sample-form')){
  250.                 $resp = ["type" => "danger", "message" => "Security Check Failed!"];
  251.             }else{
  252.                 $mfcp_meta_field = sanitize_text_field( $mfcp_meta_field );
  253.                 $mfcp_meta_value = sanitize_textarea_field( $mfcp_meta_value );
  254.                 if(empty($mfcp_id)){
  255.                     $save = $wpdb->insert(
  256.                         "{$wpdb->prefix}mfp_tbl",
  257.                         [
  258.                             "meta_field" => $mfcp_meta_field,
  259.                             "meta_value" => $mfcp_meta_value
  260.                         ],
  261.                         [
  262.                             '%s',
  263.                             '%s'
  264.                         ]
  265.                     );
  266.                 }else{
  267.                     $save = $wpdb->update("{$wpdb->prefix}mfp_tbl",
  268.                     [
  269.                         "meta_field" => $mfcp_meta_field,
  270.                         "meta_value" => $mfcp_meta_value
  271.                     ],
  272.                     [
  273.                         "id" => $mfcp_id,
  274.                     ],
  275.                     [
  276.                         '%s',
  277.                         '%s'
  278.                     ],
  279.                     [
  280.                         '%d',
  281.                     ]
  282.                     );
  283.                 }
  284.                 if($wpdb->last_error){
  285.                     $resp = ["type" => "danger", "message" => $wpdb->last_error];
  286.                 }else{
  287.                     $resp = ["type" => "success", "message" => "Data has been saved successfully!"];
  288.                 }
  289.             }
  290.            
  291.             $this->notices[] = $resp;
  292.             update_option("mfcp_notices", $this->notices);
  293.             echo "<script>location.replace(`".admin_url("admin.php?page=my-first-custom-plugin-page")."`)</script>";
  294.             exit;
  295.         }
  296.  
  297.     }
  298.  
  299.     /**
  300.         * Delete Data
  301.         */
  302.     function delete_mfcp(){
  303.         global $wpdb;
  304.         if(
  305.             isset($_GET['page']) &&
  306.             $_GET['page'] == 'my-first-custom-plugin-page-form' &&
  307.             isset($_GET['action']) &&
  308.             $_GET['action'] == 'delete_mfcp'&&
  309.             isset($_GET['mfcp_id'])
  310.         ){
  311.             $delete = $wpdb->delete("{$wpdb->prefix}mfp_tbl", [ "id" =>  $_GET['mfcp_id']], ['%d']);
  312.             if($wpdb->last_error){
  313.                 $resp = ["type" => "danger", "message" => $wpdb->last_error];
  314.             }else{
  315.                 $resp = ["type" => "success", "message" => "Data has been deleted successfully!"];
  316.             }
  317.             $this->notices[] = $resp;
  318.             update_option("mfcp_notices", $this->notices);
  319.             echo "<script>location.replace(document.referrer)</script>";
  320.             exit;
  321.         }
  322.     }
  323.    
  324. }
  325.  
  326. if(class_exists("MyFirstPlugin")){
  327.     $MyFirstPlugin = new MyFirstPlugin();
  328.     register_activation_hook(__FILE__ , array ( $MyFirstPlugin , 'activate' ) );
  329. }
  330.    

Snapshots

Explore the snapshots below, capturing the results of the Custom WordPress Plugin Script provided above:

Plugin's Custom Form Page

Custom WordPress Plugin

Plugin's Custom List Page

Custom WordPress Plugin

And there you have it! I trust this Custom WordPress Plugin Guide proves helpful for your needs and becomes a valuable resource for your current and future WordPress projects.

Consider exploring the following topics as well:

Explore more into this website for an array of Tutorials, Free Source Codes, and Articles spanning various programming languages.

Happy Coding =)

Add new comment