Building a "Recently Bought" Plugin for WooCommerce - Part 1

This is going to be a multi-part series guiding you through how to build a “Recently Bought” pop-up plugin for WooCommerce. I’ll be going through the process that I usually take to build all of my WooCommerce plugins. Usually, Id’ use my base plugin to commence a build like this, however, for the purpose of this series I will be starting completely from scratch. Well, maybe a bit of copy and paste, but I promise to explain it all!

I will be writing this series as I build it, and will release the final plugin open source via the GitHub repo. I hope you find it useful, and look forward to your feedback!

The Plugin

So, the plugin we’re going to build is a “Recently Bought” pop-up. What do I mean by that?

Well, while your customer is browsing the online store, they will see 4 or 5 items pop-up that have been bought recently. This is used on a few websites, and builds customer confidence of the store, whilst also promoting popular products.

We’ll load the data in via ajax, use cookies to make sure it’s not displayed over and over again, and add an options page where we can configure the position and some of the styling.

Some key things I’ll go through in this series, are:

  • Building a plugin using a PHP class.
  • Using tools like Gulp, Bower, and Composer.
  • Trying not to repeat yourself with code, be it PHP, SCSS/CSS, or JS.
  • Utilising Ajax in your plugin.
  • Writing neat code with useful comments.

Getting Set Up

In the first part of the tutorial, I want to focus on setting up the plugin. I have quite a specific folder structure that I use when building my plugins, and I use a few tools, like gulp, to do some fancy things with my scripts, styles, and even to compile all the relevant files into a zip file ready for distribution.

I’m going to assume that if you’re following along, you have your own local development environment already configured. Personally, I use Vagrant, VVV, and VV. I have a single WordPress install where I build and test all of my WooCommerce plugins; this resides at

I’m not going to go into detail of my local environment, but you should make sure you have these basic requirements in place before we start:

  • A local development environment (WAMP, MAMP, Vagrant, etc).
  • WordPress installed and configured.
  • WooCommerce installed and configured, along with some dummy data.
  • Some dummy WooCommerce orders. You can just add the “Cash on Delivery” payment method and run some fake orders through with a few different items.

Creating The Basic Plugin

First of all, we want to create an a plugin that can be activated. This is actually very simple.

To start, we’ll create a folder within wp-content/plugins. This folder should be named after the plugin, and it’s a good idea to prefix it with a unique key; I always use iconic-, for obvious reasons!

Let’s call our folder iconic-woo-recently-bought.

Inside this folder we’ll create the main plugin file. This should have the same name as the folder. So now we have this structure:


In our iconic-woo-recently-bought.php file, we need to create the plugin header. This is a comment block that tells WordPress some information about our plugin, like its name and author.

So open up the iconic-woo-recently-bought.php file and enter the following code:

I think the majority of that is quite self explanatory, but here’s an explanation:

  • Plugin Name
    The name of the plugin. This is what shows up in the plugin list in the WordPress admin area.
  • Plugin URI
    A link to the plugin’s homepage. in the case, the GitHub repo.
  • Description
    A description of the plugin. This also shows up in the plugin list.
  • Version
    This is the current version of the plugin. Quite important that this is updated each time a new release is made. I like to use semantic versioning, as it makes the most sense to me. They state:
    Given a version number MAJOR.MINOR.PATCH, increment the:

    1. MAJOR version when you make incompatible API changes,
    2. MINOR version when you add functionality in a backwards-compatible manner, and
    3. PATCH version when you make backwards-compatible bug fixes.
  • Author
    This is usually entered in the format of Author Name <[email protected]>.
  • Author URI
    The author’s website address.
  • Text Domain
    This one is quite important. Your plugin should always have a text domain as it makes it translation ready. More on this later.

The comment block is the minimum you need to make a plugin that can be activated in the WordPress plugin list. Of course, it won’t do anything, but it feels good to click “Activate”!

Below the comment block you can see I’ve also added a check to see if ABSPATH is defined. This prevents people accessing the code directly, avoiding potential data leaks. It’s usually a good idea to add this to any PHP file inside your plugin folder.

There you have it! A basic plugin that does nothing – fantastic! Right? I guess. But let’s keep going anyway.

Folder Structure

I want to go through the typical folder structure that all of my plugins all follow. You’ll see why as we start adding tools like gulp into the mix – I try to automate as much as I can to save time when it comes to editing and publishing new versions of my plugins.

The typical folder structure of my plugin is as follows:

  • iconic-woo-recently-bought
    • inc
      • vendor
      • admin
    • source
      • frontend
        • scss
        • js
      • admin
        • scss
        • js
    • assets
      • frontend
      • admin
      • img
      • fonts
    • languages
    • dist

Now, we might not need all of those folders for this particular project. For example, I don’t think we’ll have any admin scripts or styles. So let’s set it up as follows:

  • iconic-woo-recently-bought
    • inc
    • source
      • frontend
        • scss
        • js
    • assets
    • languages
    • dist

If we need to we can add more folders later. Go ahead and add those folders to your plugin folder.

Let me explain what these folders are for:

  • inc
    This folder will house all of our included (usually PHP based) files. This could be PHP classes, settings frameworks, admin page templates, etc.
  • source
    This is the folder where we will edit our SCSS and JS files. These folders will be “watched” and then complied and compressed into the assets folder. The files in the assets folder are the ones we’ll include on the frontend of the WordPress site. I’d recommend reading up on SCSS if you aren’t already familiar, as it is such a useful method of writing CSS.
  • assets
    As above, the files in our source folder are compiled into here automatically. As such we don’t really need to create any sub-folders in here. If we add any images or font files to the plugin, then they’d go in here too and we’d add them manually.
  • languages
    When the plugin is ready, we’ll create a .pot file that will allow users to translate any text strings in our plugin.
  • dist
    This is a folder I use primarily for when my plugins are released via CodeCanyon. I have a readme, and a zipped version of the plugin files in here. The zipped version of the plugin is created using Gulp, and I think it’s useful as you can upload it to WordPress via the backend, so this is why I have kept this folder.

Adding a Basic Constructor to the Plugin

I always write my plugins using classes. This helps prevent plugin/theme collisions, and is also just a great way to develop with PHP.

Let’s go back to our iconic-woo-recently-bought.php file. We’re going to add the main plugin class, the constructor method (a method is essentially a function within a class), and some basic setup methods.

As much as possible, we want to follow the WordPress coding standards. It helps keep things consistent throughout your plugin, and also ends up being easier to read and digest.

Within the iconic-woo-recently-bought.php, add the following code on line 16 onwards:

This is the plugin class in it’s most basic form. We have also created an instance of the class and assigned it to the $iconic_woo_recently_bought variable.

There’s certain parameters that we’ll use throughout the plugin, for example, the plugin name, slug, and folder path. For these, we’ll set up some properties (these are variables that will be used throughout the class).

Anything that isn’t a simple parameter, like plugin path and url, need to be setup when the plugin is constructed. Anything else can be defined there and then, as you can see with $name and $shortname. You’ll see where these parameters get used as we go.

So let’s add the construct method. This is the first thing that will run when we start our plugin on that last line.

Here we’re going to do a couple of things. We’re instantly going to call 2 methods (which we’ll add momentarily to the class). We’re also going to add an action that is run on the init hook.

If you’ve ever added an action in WordPress before, but not within a class, you’ll notice a slight difference. The second parameter of the add_action method can be a string or an array. If it’s a string, you simply pass the function name. If it’s an array, we’re passing the current class instance as the first array item, and the method name within our class as the second. Basically, it will run the initiate_hook method of our class.

Any method I add now will come after the __construct() method. I won’t paste the contents of the whole file anymore, just the methods as I add them.

The first method we run is textdomain(). this tells wordpress the textdomain of our plugin, and points to a languages folder where translations are help. Remember we set this up earlier as a languages folder and also the Text Domain parameter in the comment block?

We’re using the load_plugin_textdomain function, and passing in our textdomain value as the first parameter. You may be tempted to use the $slug property that we set up earlier, but it is recommended to always use a string for the textdomain, as it will be picked up more accurately by WordPresses file parsers. The second parameter is deprecated (no longer in use), and the third is the relative path to our languages folder.

Next, we run the set_constants() method. This is where we’ll assign the data to our properties from earlier.

Earlier we set up some properties for our class. Here we are setting the value to these properties. When you use a class property, you call $this->variable_name rather than the initial $variable_name you may have set earlier.

So the plugin path and plugin url are being set based on the location of the current file. We’re then setting the alternate slug property. I find it useful to have a slug with hyphens, and one with dashes, even though it may not be used.

Finally, we add a method to the init hook. This is where we’ll likely hook into some other WordPress actions/filters to implement our plugin effectively.

Here we can check whether we’re on an admin page (backend) or a customer facing page (frontend). We can add our actions/filters/methods accordingly.

Wrap Up

I’m conscious that this is quite a long post, so I think we’ll wrap it up there for now. Our structure is mainly setup, and we have a plugin that can be activated (and not do anything). We’ve also set up some properties and methods for our plugin, which we’ll expand on in part 2!

Keep an eye out for the rest of this series, which I’ll be writing once a week until it is completed. Don’t forget to check out the GitHub repo to keep an eye on the latest plugin files.

Thanks for listening, see you soon.


  1. Tazz says:

    This is awesome and I can’t wait for part 2, I’ve been looking for a while to find a good plugin to display latest purchases.

    • James Kemp says:

      Thanks for the feedback! Looking forward to writing it. I truly hope people will find it useful, even if it’s just for the final plugin!

  2. Luke Cavanagh says:

    Awesome post.

  3. chrismccoy says:

    is this the same feature the wpfomify showcases? curious this could be a good free alternative, havent found one yet

    • James Kemp says:


      Not aware of that plugin, but possibly! I didn’t end up finishing this in the end as there wasn’t much interest in it at the time.

Leave a Reply

Your email address will not be published. Required fields are marked *