Building a simple WordPress plugin is just as simple as adding a file or folder to the to your plugins directory, which is found under wp-content/plugins
. It’s a good practice to have the plugin file match the folder you put it in.
For example, if you have a folder called sample-wpajax-plugin
, the file inside it should be sample-wpajax-plugin.php
. Let’s go ahead and create the folder and plugin file inside the wp-content/plugins
folder.
The most important starter advice is getting your plugin header set the way you like it, so let’s start with that first.
Here’s an example plugin header that we’ll be using:
<?php /** * Sample WPAJAX Plugin * * @package Sample_WPAjax_Plugin * @copyright Copyright(c) 2019, MediaRon LLC * @license http://opensource.org/licenses/GPL-2.0 GNU General Public License, version 2 (GPL-2.0) * * Plugin Name: Sample WPAjax Plugin * Plugin URI: https://wpandajax.com * Description: Sample Plugin. * Version: 1.0.0 * Author: MediaRon LLC * Author URI: https://wpandajax.com * License: GPL2 * License URI: http://www.gnu.org/licenses/gpl-2.0.html * Text Domain: sample-wpajax-plugin * Domain Path: languages */
You’ll see several header options, which I’ll go over below.
sample-wpajax-plugin
and a file inside it called sample-wpajax-plugin.php
.Next up, let’s define some constants that we’ll use throughout the plugin.
define( 'SAMPLE_WPAJAX_VERSION', '1.0.0' ); define( 'SAMPLE_WPAJAX_PLUGIN_NAME', 'Sample WPAjax Plugin' ); define( 'SAMPLE_WPAJAX_DIR', plugin_dir_path( __FILE__ ) ); define( 'SAMPLE_WPAJAX_URL', plugins_url( '/', __FILE__ ) ); define( 'SAMPLE_WPAJAX_SLUG', plugin_basename( __FILE__ ) ); define( 'SAMPLE_WPAJAX_FILE', __FILE__ );
We define several constants above that can be used throughout the plugin to get version information, the plugin’s slug, the absolute path to the plugin, the URL for the plugin, and where to find the plugin’s file location.
Autoloaders are necessary in order to programmatically tell which file should be included and at just the right time and place. Without an autoloader, we’d have to manually include each PHP file you need and that’s no fun! It should be done for us. We’ll be using PHP namespaces, because honestly it makes the autoloading process cleaner and is a more modern PHP development practice.
For this, let’s introduce our plugin’s structure. There’s a structure I’ve been fond of which allows for PHP namespaces and can scale as your plugin grows larger in scope. For now, we’ll just be instantiating an asset loader which we’ll build onto as you progress through this book. You can find the sample code at https://github.com/wpajax/chapter-3 or simply go to https://github.com/wpajax to see all of the available code samples.
First, let’s go over folder structure. I have an empty CSS and JS folder, which we’ll use later to load any JavaScript or CSS that your plugin needs. I then have a PHP folder which houses the autoloader, and a plugin launch-point which we’ll use to extend the plugin.
As a convenience, here’s a graphic of the folder structure.
This plugin is housed in the wp-content/plugins
folder and can simply be activated if you download it from the GitHub repository.
Finally, here’s the autoloader code:
<?php /** * Plugin Autoloader * * @package Sample_WPAjax_Plugin */ /** * Register Autoloader */ spl_autoload_register( function ( $class ) { // Assume we're using namespaces (because that's how the plugin is structured). $namespace = explode( '\\', $class ); $root = array_shift( $namespace ); // If a class ends with "Trait" then prefix the filename with 'trait-', else use 'class-'. $class_trait = preg_match( '/Trait$/', $class ) ? 'trait-' : 'class-'; // If we're not in the plugin's namespace then just return. if ( 'Sample_WPAjax_Plugin' !== $root ) { return; } // Class name is the last part of the FQN. $class_name = array_pop( $namespace ); // Remove "Trait" from the class name. if ( 'trait-' === $class_trait ) { $class_name = str_replace( 'Trait', '', $class_name ); } $filename = $class_trait . $class_name . '.php'; // For file naming, the namespace is everything but the class name and the root namespace. $namespace = trim( implode( DIRECTORY_SEPARATOR, $namespace ) ); // Because WordPress file naming conventions are odd. $filename = strtolower( str_replace( '_', '-', $filename ) ); $namespace = strtolower( str_replace( '_', '-', $namespace ) ); // Get the path to our files. $directory = dirname( __FILE__ ); if ( ! empty( $namespace ) ) { $directory .= DIRECTORY_SEPARATOR . $namespace; } $file = $directory . DIRECTORY_SEPARATOR . $filename; if ( file_exists( $file ) ) { require_once $file; } } );
You’ll want to take note of the Sample_WPAjax_Plugin portion, which we use as the plugin’s default package and PHP namespace. You’re welcome to change this to something more generic, but you’ll need to update the plugin’s namespaces. I’ll leave that as an exercise for the reader.
Assuming you have copied the class-plugin-abstract.php
, class-plugin-interface.php
, and class-plugin.php
over from the GitHub repo, we’re ready to initialize the autoloader and main plugin file, which will be class-plugin.php
.
Here’s what we have in class-plugin.php
, which is located in the PHP folder.
<?php /** * Primary plugin file. * * @package Sample_WPAjax_Plugin */ namespace Sample_WPAjax_Plugin; /** * Class Plugin */ class Plugin extends Plugin_Abstract { /** * Execute this once plugins are loaded. */ public function plugin_loaded() { // Custom code here. } }
Let’s go back to the main plugin file, sample-wpajax-plugin.php
, and ensure the plugin_loaded
method is executed via our autoloader.
define( 'SAMPLE_WPAJAX_VERSION', '1.0.0' ); define( 'SAMPLE_WPAJAX_PLUGIN_NAME', 'Sample WPAjax Plugin' ); define( 'SAMPLE_WPAJAX_DIR', plugin_dir_path( __FILE__ ) ); define( 'SAMPLE_WPAJAX_URL', plugins_url( '/', __FILE__ ) ); define( 'SAMPLE_WPAJAX_SLUG', plugin_basename( __FILE__ ) ); define( 'SAMPLE_WPAJAX_FILE', __FILE__ ); // Setup the plugin auto loader. require_once 'php/autoloader.php';
Now let’s do some checks to make sure the PHP version is high enough. What we’re going to do is an admin notice WordPress action and then output a warning if a minimum PHP version isn’t recognized.
/** * Admin notice if Sample Plugin isn't an adequate version. */ function sample_wpajax_version_error() { printf( '<div class="error"><p>%s</p></div>', esc_html__( 'Sample WPAjax requires a PHP version of 5.6 or above.', 'sample-wpajax-plugin' ) ); } // If the PHP version is too low, show warning and return. if ( version_compare( phpversion(), '5.6', '<' ) ) { add_action( 'admin_notices', 'sample_wpajax_version_error' ); return; }
From there, we create a function which will hold the plugin’s class instance. We’ll also set up internationalization and make sure the main plugin file is running.
/** * Get the plugin object. * * @return \Sample_WPAjax\Plugin */ function sample_wpajax() { static $instance; if ( null === $instance ) { $instance = new \Sample_WPAjax_Plugin\Plugin(); } return $instance; } /** * Setup the plugin instance. */ sample_wpajax() ->set_basename( plugin_basename( __FILE__ ) ) ->set_directory( plugin_dir_path( __FILE__ ) ) ->set_file( __FILE__ ) ->set_slug( 'sample-wpajax-plugin' ) ->set_url( plugin_dir_url( __FILE__ ) ) ->set_version( __FILE__ ); /** * Sometimes we need to do some things after the plugin is loaded, so call the Plugin_Interface::plugin_loaded(). */ add_action( 'plugins_loaded', array( sample_wpajax(), 'plugin_loaded' ), 20 ); add_action( 'init', 'sample_wpajax_add_i18n' ); /** * Add i18n to Sample Plugin. */ function sample_wpajax_add_i18n() { load_plugin_textdomain( 'sample-wpajax-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' ); }
You should now have a working and installable plugin, which we’ll build upon in later chapters. For reference, the code for this chapter can be found at https://github.com/wpajax/chapter-3. Do I expect you to understand it all? Not really. But from a plugin architecture point, it’s a decent way to get started on your custom code.