How I built it: Plugin Notes Plus

Plugin Notes Plus banner

I released Plugin Notes Plus on the plugin repo in February, and it recently reached 200+ active installations. Although I built the plugin to “scratch my own itch,” it’s gratifying to know that others are finding it useful as well. I’m a big fan of the “How I Built It” podcast, so I thought it would be fun to write a post explaining how I built Plugin Notes Plus.

The Problem

The team I work with has notes in Evernote and Google Sheets with information about the plugins installed on the various sites we maintain. Crucially, some of those notes contain important information that one should know when updating the plugin. But those notes can be hard to find. What would be ideal would be to have a note right on the WordPress Admin Plugins page that would contain or link to important information about that plugin.

A survey of the existing offerings

I wasn’t the first one to have the idea to make a plugin notes plugin. Chris Coyier proposed the idea in 2009, and it was implemented by Mohammad Jangda with his plugin called Plugin Notes. I tested the plugin, and it worked as advertised except for one crucial flaw. When you click ‘delete’ to remove a note, it triggers the process of deleting the entire plugin. I found this support thread where the issue was identified, but the plugin doesn’t appear to be actively maintained.

Listening to an interview with Russell Aaron on How I Built It, I learned that the Maintainn Tools plugin offers a similar plugin notes functionality, among other things. While I liked how Maintainn Tools keeps plugin notes in a separate column rather than below the plugin description like Plugin Notes, I found it limiting that I couldn’t edit or delete the plugin notes that I had created.

Considering the limitations of the available offerings for plugin notes, I decided that there was an opportunity to build something that would better serve my team’s needs, and hopefully be of use to others as well.

How I built it

The basic idea behind the plugin is simple. When a user goes to their WordPress admin dashboard and views their installed plugins, Plugin Notes Plus provides an additional column to the right of the plugin descriptions for plugin notes. The user can add, edit, or delete notes for any installed plugin.

The plugin adds a column on the admin plugins page called “Plugin Notes” and provides functionality to add, edit, or delete notes about a plugin.

In the following sections, I describe the various considerations behind how I built the plugin in more detail.

The foundation: WordPress Plugin Boilerplate

Object-oriented programming (OOP) is a good approach for building plugins since it allows you to keep your code well organized and minimizes the chances that the code will conflict with other plugins or themes. I had learned the concepts behind OOP in introductory programming classes years ago, but I found Know the Code tutorials very helpful in getting me started with OOP PHP.

I decided to use the WordPress Plugin Boilerplate as the foundation for my plugin because it was well organized and helped to ensure that I was following best practices for plugin development. The boilerplate is organized into three main folders: ‘admin’ (for functionality in the admin), ‘public’ (for functionality on the front end), and ‘includes’ (for functionality that is shared between between the admin and front end). Because my plugin only deals with the WordPress admin, I removed the ‘public’ folder as well as any references to its files.


The user interface

Although the plugin seems simple, I spent a fair amount of time thinking through exactly how I wanted it to work. I liked how Maintainn Tools put the plugin notes in their own column and allowed for multiple notes per plugin, but I also wanted to make it possible to edit and delete notes. Thus, I used jQuery to build an interface that enables users to easily add, edit, and delete notes, and I used Ajax to handle database updates so that users can manipulate their notes without requiring a page refresh.

Note icons

I had the idea of including an icon next to each note to quickly convey what type of content it contains (e.g., info, warning, link, etc.). For that, I used Dashicons, which is the default icon font of the WordPress admin. I selected six icons that are available by default, but I also included a filter to enable developers to modify which icons are available.

Hyperlinks in notes

A feature that I liked in the original Plugin Notes plugin was that it automatically converted links to target="_blank". Presumably, if someone is looking at the Plugins page and clicks on a link in a note, the don’t want to actually leave the Plugins page.

Additionally, I converted any urls without <a> tags into links. Below is a snippet that uses a regular expression to identify any urls that aren’t links and converts them to links. I then used jQuery to add target="_blank" to all links.

function convert_urls_to_links( $input ) {
    $url_without_tags_regex = "/<a.*?<\/a>(*SKIP)(*F)|https?:\/\/\S*[^\s`!()\[\]{};:'\".,<>?«»“”‘’]/";
    $replacement_pattern = '<a href="$0">$0</a>';
    return preg_replace( $url_without_tags_regex, $replacement_pattern, $input );

Data storage

We all make mistakes, right? Well, I made a mistake when I decided where to store the plugin notes. I was familiar with the options API, so I decided to use it to store plugin notes in the options table. For each plugin with notes, I set up an array containing each plugin’s notes and note metadata and used add_option(), delete_option(), update_option(), and get_option() to handle adding, deleting, editing, and retrieving plugin notes, respectively.

The problem is that the options table is only meant to hold small amounts of information that don’t get updated often – typically setup and configuration information. Storing large amounts of data in the options table can impact performance. In fact, WP Engine will warn customers and even delete entries in the options table if they are too large.

Granted, I wouldn’t expect anyone to use Plugin Notes Plus to write an epic novel 😉 , but it’s still not a best practice to store plugin note data in the options table, as I learned after releasing the plugin and getting feedback from some early users. Returning to the codex, I reviewed my options for storing plugin notes in the database and decided that it would make the most sense to store plugin notes in their own custom database table.

In my first update (v1.1.0), I set up a new custom database table for plugin notes and added a migration routine to move any existing plugin notes out of the options table and into the new table. It felt a little like doing maintenance on a plane while flying it because I had to ensure that the update didn’t affect anyone’s existing plugin notes. However, the migration was successful, and I’m happy with how the plugin stores its data now.


Because the plugin collects and stores data in the database, security considerations were a must. Below is an overview of the security best practices that I followed while building the plugin. (I used the WordPress Plugin Handbook as a reference.)

Checking user capabilities

Any plugin that allows users to submit data should check that the user has the correct level of permissions. In the case of Plugin Notes Plus, I checked to ensure that the user had the activate-plugins capability before rendering the plugin notes and form.


After checking user capabilities, the next level of security is ensuring that the user actually intends to perform a given action. Nonces are unique, generated numbers that verify the origin and intent of requests.

Because I was using Ajax to update plugin notes, I generated a nonce using wp_create_nonce() and then made it available in the JavaScript file that handles plugin note updates using wp_localize_script(). I then checked that the nonce from the Ajax request matched the original nonce using check_ajax_referer().

Data validation and sanitization

Whenever notes are saved to the database or retrieved for rendering, they need to be validated and sanitized. For that, I wrote a process_plugin_note() function that uses wp_kses(), which filters a string of HTML to ensure that it includes only an allowed set HTML tags and attributes. For rendering translatable strings, I relied on the esc_html__() and esc_html_e() functions.

Safe database operations

Having set up a custom database table for plugin notes in the v1.1.0 release, I needed to ensure that I was following best practices when dealing with the table. I took advantage of WordPress’s built-in helper methods, including insert, update, and get_row. I also used the prepare method where appropriate to ensure that I generated SQL that was safe from SQL injection.

As an example, to retrieve a note with a particular ID, I used $wpdb->prepare() as follows:

 * Get a specific plugin note by id.
 * @since    1.1.0
public function get_plugin_note_by_id( $note_id ) {

    global $wpdb;
    $table_name = $wpdb->prefix . Plugin_Notes_Plus::get_table_name();

    $result = $wpdb->get_row( $wpdb->prepare(
        "SELECT * FROM $table_name WHERE ID = %d;",
    ) );

    $note_array = array();
    $note_array['note'] = $this->process_plugin_note( $result->note_content );
    $note_array['icon'] = $result->note_icon;
    $note_array['user'] = $result->user_name;
    $note_array['time'] = $result->time;

    return $note_array;


There were a number of ways that I could have designed the plugin to work on multisite installations. One approach would be to have the plugin notes synced across all sites. However, I opted to have each site maintain its own plugin notes. I figured that the super admin might want to keep their own private plugin notes, and then each site could add their own notes as it made sense.

One interesting issue that I encountered was that, in a multisite install, the plugin notes column didn’t display for the super admin. I eventually discovered that the manage_plugins_columns and manage_plugins_custom_columns hooks that I was using to create the new column didn’t work for the super admin. To display the plugin notes column on the network admin (super admin) plugins page, I had to additionally use these hooks: manage_plugins-network_columns and manage_plugins-network_custom_column.


I set the language for one of the sites in my local multisite install to Spanish so that I could verify that all user-facing strings could be translated. Following the instructions in this article, I used Poedit to create a Spanish translation of my plugin. I was able to confirm that all of the translated strings displayed correctly in my Spanish install.

In conclusion…

I really enjoyed building Plugin Notes Plus, and I learned a lot about plugin development. Feel free to check out the project on GitHub, or you can find it in the plugin repo if you’d like to write some plugin notes of your own.

4 thoughts on “How I built it: Plugin Notes Plus

  1. I’m the developer of a plugin called Plugin Organizer and your post on how you created Plugin Notes Plus helped me to figure out what action hook to use in order to add a column to the network plugins administration page. I’m working on adding a new feature for that page. I did figure out though that the filter hook manage_plugins-network_custom_column does not exist. I had to use manage_plugins_custom_column to add my value to the custom column I created. Your plugin calls the same function for manage_plugins-network_custom_column and manage_plugins_custom_column so it appears that the filter hook is working when it isn’t actually being called. Not a big deal. But thought you might like to know. And thank you for helping me out

    1. Hi Jeff,
      I’m glad my post was helpful, and thanks for letting me know about the hook that doesn’t exist!

Leave a Reply

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