Keeping Plugins Clean: Using Activate, Deactivate and Uninstall Hooks
Plugin authors pour so much of their time and energy into the main functionality of their products that it’s understandable less important stuff falls by the wayside.
Take activation and deactivation, for example. While activation hooks are widespread – many plugins need to add some options, flush rewrite rules, maybe create a database table or check for version differences when they are installed – deactivation and uninstallation hooks are far less less common.
My point is that many plugin authors don’t take the time to clean up after themselves. Does the WordPress installation really need the custom table you created after the plugin is removed? Why not clear out a few options exclusive to the plugin before it is trashed?
In this article I’ll show you how to use activation, deactivation and uninstallation hooks to more easily initialize your plugin, and so you can clean up after the user is done with your product. If you skim through the article, I strongly suggest taking a peek at the “Additional Security” section at the end, which complements the code with a few checks for security.
The Activation Hook
The activation hook is pretty straightforward but since installing things is a bit of a special case we’ll need to pay attention to the sequence of events. Before we go into all that, here’s the simple example:
The key to it all is the
register_activation_hook() function. The first parameter is the path to the main plugin file, the second parameter defines the function to run. Internally, the
register_activation_hook() function is a wrapper for the “activate_[plugin_name]” action, but since it’s a bit easier to use it’s unusual to see the hook in plugins.
The Installation Sequence
It’s important to understand the installation sequence because it does prevent the use of some methods you may be used to.
register_activation_hook() is called in-between the user clicking on the activation link and consequently seeing the activation notice. It runs on an intermediary page, which redirects immediately, before any hooks could have a chance of running. Let’s look at an example to see why this is a huge bummer:
Flushing Rewrite Rules
A number of plugins create custom post types. It’s a smart move to flush the rewrite rules on activation to make sure users don’t get a 404 error when visiting a post from the new custom post type. The following code seems logical but will fail.
It seems perfectly fine: The custom post type is created and on activation we flush the rewrite rules. The only problem is that the custom post types are not yet created when the rewrite rules are flushed. Here’s how the process flow looks:
- User installs the plugin
- User clicks activation link
- An intermediary page is used to run only the activation hook, nothing else. As a result, the rewrite rules are flushed.
- The plugin is active and code runs as usual, the custom post type is registered
A solution was posted on Stack Overflow, which is officially endorsed by the WordPress Codex, which solves our little problem. The solution involves adding an option, which indicates the plugin was just installed.
If this option exists we do our activation stuff and delete it. Something like this:
I personally don’t like this solution too much. The problem is that the check on line eight runs on every single page load. There’s nothing to be afraid of, it won’t put a big load on your servers, it won’t slow down the website for your users. It’s a very quick check which has negligible performance impact, however it is unnecessary 99.9% of the time.
This is why there actually is a better solution, mentioned in the Codex in the documentation for the
flush_rewrite_rules() function. In this solution we use the modularity of our functions to register the custom post type on activation separately:
Instead of relying on a check, which needs to run all the time, we use the activation function to register our post types. Note that once our plugin is activated the post types will always be registered from the
This is a sad example of the Codex being all over the place. Generally, WordPress does have good documentation, but if something seems wasteful or illogical, don’t be afraid to do some research of your own.
Creating Database Tables
Another task some plugins perform is creating database tables. Take care as this is unnecessary more often than not, but there are some legit use cases. Here’s an example from the Creating Tables Codex article, which also shows us how multiple activation calls can be used:
The first function,
jal_install() creates a new database table. The second function,
jal_install_data adds initial data to the table. Instead of using
register_activation_hook() to add one function, which contains all this code, we can use
register_activation_hook multiple times.
This is great practice for modularity. On one hand you don’t have to add initial test data – it’s as simple as removing the activation hook – so you can keep the function in tact. On the other hand, you are free to re-use these functions anywhere since they are well-separated.
Another common task for the activation function is checking for dependencies. Your plugin may rely on a specific version of WordPress, another plugin or even a specific version of PHP. The following code checks for a minimum WP and PHP version and redirects the user (without activating the plugin) if necessary:
The Deactivation Hook
Deactivation hooks run when a user has deactivated a plugin, but before it is uninstalled (deleted). Their use is identical to activation hooks:
Deactivation only means that the user has deactivated your plugin so you won’t want to do as much as during an uninstall. You may want to flush rewrite rules perhaps but at this stage you won’t want to get rid of all your options and your database table if you have one.
This is pretty straightforward but I will pay special attention to flushing rewrite rules as these are problematic once again. The codex recommends doing them the following way, but this does not work:
The reason this doesn’t work has to do with the same type of reasoning as we had before. When deactivation runs the
init hook has already been performed, which means that as we are deactivating our custom post type is registered. The rules are indeed flushed but they take into account the custom post type.
A Trac ticket is in place to tackle this but until then I actually can’t give you a very good way of doing this. The only way I’ve found that works is to delete the rewrite rules altogether:
While this has worked for me in the past I would not recommend this either. The uncertainty this introduces is greater than the problem of having a few extra rewrite rules. I would rather display a note to users asking them to visit the permalink settings after deactivation which would flush the rewrite rules. Until a solution is implemented we’re stuck with this, sorry!
The Uninstallation Hook
There are two ways to run code when a plugin is uninstalled. You can use the uninstallation hook via
register_uninstall_hook() or you can use a dedicated
uninstall.php file within your plugin. While I will show you both, the preferred method is using the uninstall file.
The main issue with the uninstallation hooks is that
“It prevents the main plugin file from being run during uninstall, which can be problematic if the plugin runs code in the global space. It’s also better in that the uninstall code is centralized.”
The code below shows the uninstallation process using a basic hook:
As discussed, this is not the best solution. A far better way of handling uninstallations is the
uninstall.php file. All you need to do is create it, it will be used if it is available.
As you can see, this is actually a simpler solution and best of all, it is self-contained.
I didn’t want to over-complicate the examples so far with security, but you really should take some steps to ensure that only those allowed to do so can run these actions. The following snippet should be used on activation and deactivation:
This code block makes sure that the user has the permissions to perform this action and that the action originated on the proper page. This should protect against most malicious attempts.
The uninstallation process is special so we’ll need to use slightly different code:
If your plugin adds stuff to WordPress it’s your duty as a developer to remove it when the user decides to delete your plugin.
Using the activation, deactivation and uninstallation methods outlined here you can build a system which does this safely and securely.
I highly recommend reading this Stackexchange thread, which outlines these processes in OOP environments as well.
Do you find that plugins leave your database in a mess when you remove them? Let us know what you think in the comments below.