Plugins

This article is part of the WordPress guide. Read the introduction.

We have already covered the first 2 out of 3 major layers of WordPress – content and presentation. It’s time for the last one – functionality.

You add functionality to WordPress websites with plugins. This decouples the presentation layer (themes) from the functionality layer. Some of the things we’ve already discussed that you should do in plugins are:

  • Adding custom post types
  • Adding shortcodes
  • Adding blocks
  • Adding widgets

Plugins are only possible because of WordPress’s event driven architecture, powered by its hooks system. If you’re feeling a bit rusty in hooks, this might be a good time to revisit that chapter.

At the very lowest level, a plugin is just one PHP file with a comment header, placed in the /wp-content/plugins directory. This tiny system, coupled with access to all of WordPress’s hooks, unlocks virtually limitless possibilities.

To demonstrate plugins, we’ll create a very simple Post Reading Time plugin, displaying the estimated reading time of a post. This will give us an opportunity to demonstrate every major concept while being simple enough to not clutter the document with too much code. We will continue extending this plugin with additional functionality throughout subsequent parts of this guide.

The official and up-to-date documentation of plugins is available in the Plugin Handbook.

Best Practices

There are a few best practices you should keep in mind when developing a plugin.

Avoid Naming Collisions

That shouldn’t be a surprise to you. We’ve already been doing that with the thm_ prefix. You should prefix all of your global-facing touchpoints with a unique slug. That includes, but is not limited to:

  • functions
  • classes
  • variables
  • options
  • custom database tables
  • CSS classes

Alternatively, you may consider using unique PHP namespaces to hide them from the global namespace. Another viable and popular approach is to declare all of your methods in a class instead of as functions. The benefit is that you only have to ensure the class’s name is unique, and you don’t have to worry about its methods.

Whatever you do, just keep in mind that your plugin has to be interoperable with the thousands of other plugins available in the WordPress ecosystem. We will use a pgn_ prefix (although it’s recommended for your prefix to be at least 5 characters long).

Check For ABSPATH

‘ABSPATH’ is a global constant defined by WordPress during its boot process. It’s the de-facto way of checking if the file has been loaded by WordPress or if it’s been accessed directly. You should check it in all of your PHP files.

PHP
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

If you don’t do that, someone can access your script by navigating directly to https://domain.com/wp-content/my-plugin/insecure-file.php. The code will be run without the WordPress core ever being loaded.

Best case scenario – the user gets a 500 status code because the used wp functions aren’t defined. Worst case scenario – the code messes with the database or files on your server, corrupting your website or introducing vulnerabilities. Get in the habit of checking for ABSPATH.

Don’t Reinvent The Wheel

This should go without saying, but remember to always do things the WordPress way (unless you have a really good reason not too). One of the biggest problems with plugins, and why it’s so hard to maintain websites with too many of them, is incompatibility.

Use core APIs whenever you can. Don’t try to go around what everybody else is doing and don’t try to fight the framework. You’re risking incompatibilities with other plugins and with WordPress itself if it ever introduces breaking changes. The core APIs will surely be adapted – your code will not.

Plugin Architecture

It’s important to understand that your plugin is basically a full-fledged backend application. You have complete control over everything that happens. You have no template hierarchy that you have to abide by. Your single main file is loaded by WordPress, and that’s the front controller to your code.

You can create as many or as few files as you want. You can use whatever coding practices you want, be it procedural or object-oriented. Hell, you can even use the MVC pattern in your plugins. No one is going to stop you.

The Main File

A plugin can consist of only one file in the /wp-contents/plugins directory. You don’t even need a folder for this file. WordPress parses all the files in this directory (and subdirectories) and looks for specific header comments. A file with such comments becomes the main plugin file. Here is an example:

PHP
/*
 * Plugin Name:       My Basics Plugin
 * Plugin URI:        https://example.com/plugins/the-basics/
 * Description:       Handle the basics with this plugin.
 * Version:           1.10.3
 * Requires at least: 5.2
 * Requires PHP:      7.2
 * Author:            John Smith
 * Author URI:        https://author.example.com/
 * License:           GPL v2 or later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Update URI:        https://example.com/my-plugin/
 * Text Domain:       my-basics-plugin
 * Domain Path:       /languages
 * Requires Plugins:  my-plugin, yet-another-plugin
 */

I copied this from the official documentation. I’m not going to explain every single attribute. You can look them up any time. The only required field is ‘Plugin Name’. If that is present, the plugin will be recognized and displayed in the admin panel. You should only have one main file for your plugin.

Code & File Structure

As I already explained, you can structure your plugin whatever way you want. This is not a programming tutorial so I’m not going to teach you the best way to write readable code. What I will show you are some commonly seen patterns among plugin developers.

Single File

This is the simplest structure ever. You only have your main file. This single file contains all of your code: functions, classes, hooks, etc. It is very simple and readable, until it’s not. If your plugin has over 200 lines of code, it’s usually a sign you should use a different structure.

This is what we are going to be using for our Post Reading Time plugin. We will also not use OOP, just functions. That’s because it’s the simplest and least verbose, which is important for being able to show it in this guide.

Multiple Files – Only Functions

This is a step in the right direction. It ensures a better structure of the project by splitting different parts into different files. The potential problem is that it still operates in the C-style, procedural paradigm, with functions and file imports. You can do that if you like, but the object-oriented paradigm is usually preferred.

A potential file structure might look like this:

  • my-plugin/
    • my-plugin.php (main file)
    • includes/
      • functions.php
      • admin.php
      • shortcodes.php
    • assets/
      • css/
      • js/
    • languages/

Single Class

This can either be all in the main file or with a separate class file. The defining factor is the use of a single class, usually following the Singleton pattern. You might use that as a technique to avoid naming collisions of your methods.

Some functionalities can naturally be modeled using a single class, or it might be an intermediary state that will change as the scope of the plugin grows. Either way, OOP is usually a step in the right direction.

Multiple Classes

This is where most commercial plugins live. It’s just a typical OOP application with classes and objects interacting with each other. You might have separate classes for handling the admin and frontend sides.

A potential file structure might look like this:

  • my-plugin/
    • my-plugin.php (main file)
    • includes/
      • class-my-plugin.php
      • class-my-plugin-admin.php
      • class-my-plugin-public.php
    • assets/
      • css/
      • js/
    • languages/


And then you might see something like this somewhere in the code:

PHP
if ( is_admin() ) {
	$admin = new My_Plugin_Admin();
	$admin->run();
} else {
	$public = new My_Plugin_Public();
  $public->run();
}

Post Reading Time Plugin (Code Example)

Let’s write our plugin. Here’s how it’ll work. We’ll hook into the the_content filter which runs on every the_content() call and passes the $content as an argument. We’ll strip the HTML tags, calculate the number of words, and divide by 250 which is the average reading speed. We’ll then display our estimated rounded up reading time before the actual content. Here’s the entire code:

PHP
<?php
/*
* Plugin Name: Post Reading Time
*/

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

function pgn_calculate_reading_time( $content ) {
    $avg_words_per_minute = 250;
    $word_count = str_word_count( strip_tags( $content ) );
    $reading_time = $word_count / $avg_words_per_minute;

    return ceil( $reading_time );
}

function pgn_add_snippet_to_post_content( $content ) {
   if ( ! is_single() || ! is_main_query() || ! in_the_loop() ) {
        return $content;
    }

    $reading_time = pgn_calculate_reading_time( $content );
    $display_html = '<div class="pgn-reading-time">Reading time: ' . $reading_time . ' min</div>';

    return $display_html . $content;
}
add_filter( 'the_content', 'pgn_add_snippet_to_post_content' );

That’s it. The plugin is done. You can publish it on the Plugin Directory and add “WordPress Plugin Developer” to your LinkedIn profile.