{"id":136438,"date":"2015-01-23T08:00:58","date_gmt":"2015-01-23T13:00:58","guid":{"rendered":"http:\/\/premium.wpmudev.org\/blog\/?p=136438"},"modified":"2022-04-01T00:49:11","modified_gmt":"2022-04-01T00:49:11","slug":"activate-deactivate-uninstall-hooks","status":"publish","type":"post","link":"https:\/\/wpmudev.com\/blog\/activate-deactivate-uninstall-hooks\/","title":{"rendered":"Using WordPress Hooks to Clean Up Code, Activate &amp; Uninstall"},"content":{"rendered":"<p>Plugin authors devote so much time and energy into the main functionality of their products, that they allow less important stuff to fall by the wayside.<\/p>\n<p>Take activation and deactivation, for example. While activation hooks are widespread\u00a0\u2013 many\u00a0plugins need to add some options, flush rewrite rules, maybe create a database table, or check for version differences on installation \u2013 deactivation and uninstallation hooks are far less common.<\/p>\n<p>The point here? Many plugin authors don&#8217;t take the time to clean up after themselves. Does the WordPress installation really need the custom table you created after removing the plugin? Why not clear out a few options that are exclusive to the plugin before trashing it?<\/p>\n<p>In this article, I&#8217;ll show you how to use activation, deactivation and uninstallation hooks to initialize your plugin and clean things up more easily after users are done with your product.<\/p>\n<ul>\n<li><a href=\"#activation\">The Activation Hook<\/a>\n<ul>\n<li><a href=\"#installation\">The Installation Sequence<\/a><\/li>\n<li><a href=\"#flushing\">Flushing Rewrite Rules<\/a><\/li>\n<li><a href=\"#tables\">Creating Database Tables<\/a><\/li>\n<li><a href=\"#dependency\">Dependency Checks<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#deactivation\">The Deactivation Hook<\/a><\/li>\n<li><a href=\"#uninstallation\">The Uninstallation Hook<\/a><\/li>\n<li><a href=\"#security\">Additional Security<\/a><\/li>\n<li><a href=\"#cleanup\">It\u2019s Time To Clean Up<\/a><\/li>\n<\/ul>\n<p>Note: If you plan to skim through this article, I strongly suggest taking a peek at the &#8220;Additional Security&#8221; section at the end, which complements the code with some valuable security checks. Also, if you need help with WordPress plugin hooks, here is a quick refresher on\u00a0<a href=\"https:\/\/wpmudev.com\/blog\/understanding-using-wordpress-hooks\/\" target=\"_blank\" rel=\"noopener\">using WordPress hooks<\/a> and <a href=\"https:\/\/wpmudev.com\/blog\/how-to-write-and-activate-a-function-in-wordpress\/\" target=\"_blank\" rel=\"noopener\">how to activate a function in WordPress<\/a>.<\/p>\n<h2 id=\"activation\">The Activation Hook<\/h2>\n<p>Although the activation hook is quite straightforward, installing it is a bit of a special case, so we&#8217;ll need to pay attention to the sequence of events. Before we go into all this, here&#8217;s a simple example:<\/p>\n<div class=\"gist\" data-gist=\"f356a899b7f009d136644383339db4f6\" data-gist-file=\"basic-example.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/f356a899b7f009d136644383339db4f6.js?file=basic-example.php\">Loading gist f356a899b7f009d136644383339db4f6<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>The key to it all is the <code>register_activation_hook()<\/code> function. The first parameter is the path to the main plugin file; the second parameter defines the function to run. Internally, the <code>register_activation_hook()<\/code> function is a wrapper for the &#8220;activate_[plugin_name]&#8221; action, but since it&#8217;s a bit easier to use, it&#8217;s unusual to see the hook in plugins.<\/p>\n<h3 id=\"installation\">The Installation Sequence<\/h3>\n<p>Understanding the installation sequence is important because it prevents using methods you may be used to. <code>register_activation_hook()<\/code> 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 can have a chance to run.<\/p>\n<p>Let&#8217;s look at an example to see why this is a huge bummer:<\/p>\n<h3 id=\"flushing\">Flushing Rewrite Rules<\/h3>\n<p>A number of plugins create custom post types. Flushing the rewrite rules on activation to make sure that users don&#8217;t get a 404 error when visiting a post from the new custom post type is a smart move.<\/p>\n<p>The code below seems logical but will fail.<\/p>\n<div class=\"gist\" data-gist=\"7c656623608efd1785437ebe7cdb7350\" data-gist-file=\"incorrect-cpt.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/7c656623608efd1785437ebe7cdb7350.js?file=incorrect-cpt.php\">Loading gist 7c656623608efd1785437ebe7cdb7350<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>It <em>seems<\/em> perfectly fine. The custom post type is created and on activation, we flush the rewrite rules. The problem is that the custom post types have not yet been created when we flush the rewrite rules.<\/p>\n<p>Here&#8217;s how the process flow looks:<\/p>\n<ol>\n<li>The user installs the plugin.<\/li>\n<li>The user clicks the activation link.<\/li>\n<li>An intermediary page runs the activation hook only, nothing else. This flushes the rewrite rules.<\/li>\n<li>The plugin is active and code runs as usual.\u00a0 The custom post type is registered.<\/li>\n<\/ol>\n<p>A solution posted on <a href=\"http:\/\/stackoverflow.com\/questions\/7738953\/is-there-a-way-to-determine-if-a-wordpress-plugin-is-just-installed\/13927297#13927297\" target=\"_blank\">Stack Overflow<\/a>, which is officially endorsed by the\u00a0<a href=\"https:\/\/developer.wordpress.org\/reference\/functions\/register_activation_hook\/\" target=\"_blank\">WordPress Codex<\/a>, solves our little problem. The solution involves adding an option to indicate that the plugin has just been installed.<\/p>\n<p>If this option exists, we do our activation stuff and then delete it.<\/p>\n<p>Something like this:<\/p>\n<div class=\"gist\" data-gist=\"6f0927b3bf9807e426c8778a3bf3a797\" data-gist-file=\"option-activation.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/6f0927b3bf9807e426c8778a3bf3a797.js?file=option-activation.php\">Loading gist 6f0927b3bf9807e426c8778a3bf3a797<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>Personally, I don&#8217;t like this solution too much. The problem is that the check on line eight runs on every single page load. It&#8217;s nothing to be concerned about, as it won&#8217;t put a big load on your servers and it won&#8217;t slow down the website for your users. It&#8217;s a very quick check with a negligible impact on performance. It is, however, unnecessary 99.9% of the time.<\/p>\n<p>There is a better solution mentioned in\u00a0the Codex in the documentation for the <a href=\"http:\/\/codex.wordpress.org\/Function_Reference\/flush_rewrite_rules\" target=\"_blank\"><code>flush_rewrite_rules()<\/code><\/a> function. In this solution, we use\u00a0the modularity of our functions to register the custom post type on activation separately:<\/p>\n<div class=\"gist\" data-gist=\"b44bf08bf511277184a49de53c0c3ed8\" data-gist-file=\"best-activation.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/b44bf08bf511277184a49de53c0c3ed8.js?file=best-activation.php\">Loading gist b44bf08bf511277184a49de53c0c3ed8<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>Instead of relying on a check that 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 <code>init<\/code> hook.<\/p>\n<p>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&#8217;t be afraid to do some research of your own.<\/p>\n<h3 id=\"tables\">Creating Database Tables<\/h3>\n<p>Another task that some plugins perform is to create database tables. More often than not, this is unnecessary, but there are some legit use cases.<\/p>\n<p>This example from the Codex article on\u00a0<a href=\"http:\/\/codex.wordpress.org\/Creating_Tables_with_Plugins\" target=\"_blank\">Creating Tables<\/a>\u00a0shows how multiple activation calls can be used:<\/p>\n<div class=\"gist\" data-gist=\"9a1d4757d023f2442093a9a158cdb6b4\" data-gist-file=\"create-tables.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/9a1d4757d023f2442093a9a158cdb6b4.js?file=create-tables.php\">Loading gist 9a1d4757d023f2442093a9a158cdb6b4<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>The first function,\u00a0<code>jal_install()<\/code> creates a new database table. The second function, <code>jal_install_data<\/code> adds initial data to the table. Instead of using <code>register_activation_hook()<\/code> to add one function containing all of this code, we can use <code>register_activation_hook<\/code> multiple times.<\/p>\n<p>This is a great practice for modularity. On one hand, you don&#8217;t <em>have<\/em> to add initial test data \u2013 it&#8217;s as simple as removing the activation hook \u2013 so you can keep the function intact. On the other hand, you are free to reuse these functions anywhere since they are separate.<\/p>\n<h3 id=\"dependency\">Dependency Checks<\/h3>\n<p>Another common task for the activation function is to check for dependencies. Your plugin may rely on a specific version of WordPress, another plugin, or even a specific version of PHP.<\/p>\n<p>The code below checks for a minimum WP and PHP version and redirects the user (without activating the plugin) if necessary:<\/p>\n<div class=\"gist\" data-gist=\"79a2c5414969291ec90cac11c38b7522\" data-gist-file=\"dependency-check.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/79a2c5414969291ec90cac11c38b7522.js?file=dependency-check.php\">Loading gist 79a2c5414969291ec90cac11c38b7522<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<h2 id=\"deactivation\">The Deactivation Hook<\/h2>\n<p>Deactivation hooks run when a user has deactivated a plugin, but before it is uninstalled (deleted). Deactivation hooks are used in the same way as activation hooks:<\/p>\n<div class=\"gist\" data-gist=\"6ed9bb66ee1863ab3e84db1f9f753792\" data-gist-file=\"basic-deactivation.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/6ed9bb66ee1863ab3e84db1f9f753792.js?file=basic-deactivation.php\">Loading gist 6ed9bb66ee1863ab3e84db1f9f753792<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>Deactivation means that the user has only deactivated your plugin, so you won&#8217;t want to do as much as you would during an uninstall. You may want to flush rewrite rules perhaps, but at this stage, you won&#8217;t want to get rid of all your options and your database table (if you have one).<\/p>\n<p>This is fairly straightforward but I will pay special attention to flushing rewrite rules as, once again, these are problematic.<\/p>\n<p>The codex recommends doing them as shown below, but this <strong>does not work<\/strong>:<\/p>\n<div class=\"gist\" data-gist=\"4440a0178b4e34506530e13d0ead8958\" data-gist-file=\"incorrect-deactivation-flush.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/4440a0178b4e34506530e13d0ead8958.js?file=incorrect-deactivation-flush.php\">Loading gist 4440a0178b4e34506530e13d0ead8958<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>The reason this doesn&#8217;t work is the same as before. Running a deactivation performs the <code>init<\/code> hook, which means that as we are deactivating our plugin, we are also registering our custom post type. The rewrite rules are flushed, but they take into account the custom post type.<\/p>\n<p>A <a href=\"https:\/\/core.trac.wordpress.org\/ticket\/29118#comment:14\" target=\"_blank\">Trac ticket<\/a> is in place to tackle this, but until then, I can&#8217;t give you a very good way of doing this. The only way I&#8217;ve found that works is to delete the rewrite rules altogether:<\/p>\n<div class=\"gist\" data-gist=\"98b496826278084a2f7a5ea27994f781\" data-gist-file=\"delete-rewrite-rules.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/98b496826278084a2f7a5ea27994f781.js?file=delete-rewrite-rules.php\">Loading gist 98b496826278084a2f7a5ea27994f781<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>While this has worked for me in the past, I would not recommend it. It introduces a greater uncertainty 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 better solution is implemented, we&#8217;re stuck with this&#8230; sorry!<\/p>\n<h2 id=\"uninstallation\">The Uninstallation Hook<\/h2>\n<p>There are two ways to run code when uninstalling a plugin. You can use the uninstallation hook via <code>register_uninstall_hook()<\/code> or you can use a dedicated <code>uninstall.php<\/code> file within your plugin. I will show you both, but the <a href=\"http:\/\/wptavern.com\/plugin-developers-use-uninstall-php-please\" target=\"_blank\">preferred method<\/a> is to use the uninstall file.<\/p>\n<p>The main issue with the uninstallation hook is that &#8220;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\u2019s also better in that the uninstall code is centralized.&#8221; &#8211; <cite><a href=\"http:\/\/profiles.wordpress.org\/coffee2code\" target=\"_blank\">Scott Riley<\/a><\/cite><\/p>\n<p>The code below shows the uninstallation process using a basic hook:<\/p>\n<div class=\"gist\" data-gist=\"040847db4739148900b1ee29d227d71d\" data-gist-file=\"uninstall-hook.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/040847db4739148900b1ee29d227d71d.js?file=uninstall-hook.php\">Loading gist 040847db4739148900b1ee29d227d71d<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>As discussed, this is not the best solution. A far better way to handle uninstallations is to use the <code>uninstall.php<\/code> file. All you need to do is create it and it will be used if it is available.<\/p>\n<div class=\"gist\" data-gist=\"44dde25dcb57b4239be8586f4d04c765\" data-gist-file=\"uninstall.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/44dde25dcb57b4239be8586f4d04c765.js?file=uninstall.php\">Loading gist 44dde25dcb57b4239be8586f4d04c765<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>As you can see, this is actually a simpler solution. Best of all, it is self-contained.<\/p>\n<h2 id=\"security\">Additional Security<\/h2>\n<p>I didn&#8217;t want to over-complicate the examples shown so far with security-related issues, but you really should take some steps to ensure that only those who are allowed to do so can run these actions.<\/p>\n<p>Use the following snippet on activation and deactivation:<\/p>\n<div class=\"gist\" data-gist=\"357037989065f89c15f049314e9831bf\" data-gist-file=\"security.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/357037989065f89c15f049314e9831bf.js?file=security.php\">Loading gist 357037989065f89c15f049314e9831bf<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<p>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.<\/p>\n<p>The uninstallation process is special, so we&#8217;ll need to use slightly different code:<\/p>\n<div class=\"gist\" data-gist=\"541c93cfa9b89e1e6c7b48b06732d31f\" data-gist-file=\"security-uninstall.php\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/541c93cfa9b89e1e6c7b48b06732d31f.js?file=security-uninstall.php\">Loading gist 541c93cfa9b89e1e6c7b48b06732d31f<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n<h2 id=\"cleanup\">It&#8217;s Time To Clean Up<\/h2>\n<p>If your plugin adds stuff\u00a0to WordPress, then it&#8217;s your duty as a developer to remove it when\u00a0a user decides to delete\u00a0your plugin.<\/p>\n<p>Using the activation, deactivation and uninstallation methods outlined above will allow you to build a system that does this safely and securely.\u00a0I also highly recommend reading <a href=\"http:\/\/wordpress.stackexchange.com\/questions\/25910\/uninstall-activate-deactivate-a-plugin-typical-features-how-to\/25979#25979\" target=\"_blank\">this Stackexchange thread<\/a>, which outlines these processes in OOP environments.<\/p>\n<p>If you&#8217;re not a WPMU DEV member yet, sign up today for a <a href=\"https:\/\/wpmudev.com\/#trial\" target=\"_blank\" rel=\"noopener\">free trial<\/a>, completely risk-free. As a member, you&#8217;ll get access to all of our great plugins and blazing fast hosting service, plus expert 24\/7 support for all your WordPress-related questions and issues.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Plugin authors spend so much time working on the functionality of their products that they forget to tidy up after themselves! The answer? Deactivation and uninstallation hooks.<\/p>\n","protected":false},"author":774618,"featured_media":181139,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"blog_reading_time":"","wds_primary_category":0,"wds_primary_tutorials_categories":0,"footnotes":""},"categories":[263],"tags":[10315,11188],"tutorials_categories":[],"class_list":["post-136438","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-wordpress-developers","tag-wordpress-hooks"],"_links":{"self":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/136438","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/users\/774618"}],"replies":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/comments?post=136438"}],"version-history":[{"count":44,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/136438\/revisions"}],"predecessor-version":[{"id":208460,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/136438\/revisions\/208460"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/media\/181139"}],"wp:attachment":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/media?parent=136438"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/categories?post=136438"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/tags?post=136438"},{"taxonomy":"tutorials_categories","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/tutorials_categories?post=136438"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}