I've researched this a few times, yet my searching does not reveal much except custom code which may or may not be good WordPress practice.

As of the latest releases (WordPress 3.9 "Smith"), has a hook been added to the plugin update process? I'm asking because its a very basic need, yet I do not see it added to the codex (yet). If not, what is the common and best practice developers employ?

EDIT: Just to clarify, I'm not talking about activation, but about updating, that way, if there are changes in database or otherwise it can be addressed.

  • duplicate of wordpress.stackexchange.com/questions/61456/…
    – drzaus
    Sep 9, 2014 at 9:03
  • @drzaus answer provided there is not a good practice. Aug 5, 2016 at 16:32
  • @RensTillmann asides from this being 2 years out of date anyway, the linked q/a has basically the same answer but predates this question by another 2 years, hence the 'duplicate'.
    – drzaus
    Aug 8, 2016 at 19:45

5 Answers 5


I don't think an action has been added. You can look at version details for any version and see any new actions added.

The WordPress Way to run code on plugin update is what is described here:

The proper way to handle an upgrade path is to only run an upgrade procedure when you need to. Ideally, you would store a “version” in your plugin’s database option, and then a version in the code. If they do not match, you would fire your upgrade procedure, and then set the database option to equal the version in the code. This is how many plugins handle upgrades, and this is how core works as well.

and with code example here:

function myplugin_update_db_check() {
    global $jal_db_version;
    if (get_site_option( 'jal_db_version' ) != $jal_db_version) {
add_action( 'plugins_loaded', 'myplugin_update_db_check' );
  • Thank you - I'll simply use that method then. WP really has to add an action for this :D May 20, 2014 at 2:20
  • 9
    technically you're supposed to use register_activation_hook, since in most cases a plugin gets deactivated/activated whenever you update it from the admin. Hooking to plugins_loaded will do your check on every page load (including frontend). There was talk about introducing register_update_hook, but it was marked as WONTFIX a while ago. The discussion there is helpful.
    – drzaus
    Sep 9, 2014 at 9:10
  • 4
    It's important to understand that a mass plugin update does NOT run activation hooks - it SHOULD, but doesn't at 3.9.2. By "mass update" I mean an update done from the Dashboard update page. Individual updates done from within the plugin page run the hooks just fine.
    – Brian C
    Sep 21, 2014 at 0:26
  • 6
    The thing is plugins can also be updated via FTP which means that the hook will not be fired in any case. That's why you need to resort to the option stored in the database.
    – giraff
    Sep 1, 2015 at 16:37
  • 5
    To expand on @giraff's comment, the same is true for people who manage their code with source control like SVN or Git. Because of that, this answer is the best way to handle upgrades. Sep 10, 2015 at 16:53

Since WordPress 3.9 you can use upgrader_process_complete hook.
This hook will be fire when upgrader process is complete (plugins and themes are updated).

See reference 1, 2, 3

Here is an example code:

 * Plugin Name: Test plugin 1
 * Plugin URI: https://rundiz.com
 * Description: A very simple plugin for testing. This plugin do nothing.
 * Version: 0.1.8
 * Author: Vee Winch
 * Author URI: http://rundiz.com
 * License: MIT
 * License URI: https://opensource.org/licenses/MIT
 * Text Domain: test-plugin1
 * Domain Path: 

add_action('upgrader_process_complete', 'testplugin_upgrade_completed', 10, 2);
 * Upgrader process complete.
 * @see \WP_Upgrader::run() (wp-admin/includes/class-wp-upgrader.php)
 * @param \WP_Upgrader $upgrader_object
 * @param array $hook_extra
function testplugin_upgrade_completed(\WP_Upgrader $upgrader_object, $hook_extra)
    // get current plugin version. ( https://wordpress.stackexchange.com/a/18270/41315 )
        require_once(ABSPATH . 'wp-admin/includes/plugin.php');
    // https://developer.wordpress.org/reference/functions/get_plugin_data/
    $plugin_data = get_plugin_data(__FILE__);
    $plugin_version = ($plugin_data['Version'] ?? 'unknown.version');

    if (
        is_array($hook_extra) && 
        array_key_exists('action', $hook_extra) && 
        $hook_extra['action'] == 'update'
    ) {
        if (
            array_key_exists('type', $hook_extra) && 
            $hook_extra['type'] == 'plugin'
        ) {
            // if updated the plugins.
            $this_plugin = plugin_basename(__FILE__);
            $this_plugin_updated = false;
            if (array_key_exists('plugins', $hook_extra)) {
                // if bulk plugin update (in update page)
                foreach ($hook_extra['plugins'] as $each_plugin) {
                    if ($each_plugin === $this_plugin) {
                        $this_plugin_updated = true;
                }// endforeach;
            } elseif (array_key_exists('plugin', $hook_extra)) {
                // if normal plugin update or via auto update.
                if ($this_plugin === $hook_extra['plugin']) {
                    $this_plugin_updated = true;
            if ($this_plugin_updated === true) {
                // if this plugin is just updated.
                // do your task here.
                // DON'T process anything from new version of code here, because it will work on old version of the plugin.
                // please read again!! the code run here is not new (just updated) version but the version before that.
                file_put_contents(WP_CONTENT_DIR . '/test.txt', 'v'.$plugin_version."\r\n", FILE_APPEND);
                // set transient to let it run later.
                set_transient('testplugin_just_updated', 1);
        } elseif (
            array_key_exists('type', $hook_extra) && 
            $hook_extra['type'] == 'theme'
        ) {
            // if updated the themes.
            // same as plugin, the bulk theme update will be set the name in $hook_extra['themes'] as 'theme1', 'theme2'.
            // normal update or via auto update will be set the name in $hook_extra['theme'] as 'theme1'.
    }// endif; $hook_extra
}// testplugin_upgrade_completed

add_action('plugins_loaded', 'testplugin_pluginloaded');
 * Run once plugin loaded (on every page load).
function testplugin_pluginloaded()
    // get current plugin version. ( https://wordpress.stackexchange.com/a/18270/41315 )
        require_once(ABSPATH . 'wp-admin/includes/plugin.php');
    // https://developer.wordpress.org/reference/functions/get_plugin_data/
    $plugin_data = get_plugin_data(__FILE__);
    $plugin_version = ($plugin_data['Version'] ?? 'unknown.version');

    if (get_transient('testplugin_just_updated') && current_user_can('manage_options')) {
        // if it was marked in transient that this plugin just updated and current user is admin.
        // you can use new verion of code here.
        file_put_contents(WP_CONTENT_DIR . '/test-update-by-transient.txt', 'v'.$plugin_version."\r\n", FILE_APPEND);

        // your update code here.
        // delete transient when done to do not let this code run again.
}// testplugin_pluginloaded

The upgrader_process_complete hook will be run with your current version of code while the plugin/theme is updating. It is not use new version.


  1. You have plugin version 1.0
  2. You run the update page or auto update.
  3. Your plugin version 2.0 will be downloaded and extract. The upgrader_process_complete hook will be called.
  4. Your plugin version 1.0 will be run in upgrader_process_complete hook.
  5. Once done, reload your page and the plugins_loaded hook will be called.
  6. Your plugin version 2.0 run in plugins_loaded hook. (The plugin must be activated.)

These are already explained in the code I have posted from earlier (before edit) but maybe not clearly or hard to see it.

The upgrader_process_complete hook is created for this (please read in the reference link 3). To run after upgrade completed.

You may use plugins_loaded hook with the code in accepted answer. It did work and write shorter or you may have any better idea to use with upgrader_process_complete hook.

The upgrader_process_complete hook will be work when:

  1. Update via the update page.
  2. Update via the plugins or themes page.
  3. Update via auto update or WP Cron.

The code above doesn't work when you update plugin or theme via FTP because it can't detected transient option. In this case, the accepted answer is the only best option for you.

  • 1
    This hook sounds good at first, but if you check the codex, there is a warning that says it runs code from the prior version of the plugin. This is because this hook doesn't run after the entire update process. It actually runs after the download is complete and before the old version is even deleted.
    – user54141
    Oct 26, 2021 at 19:10
  • @user54141 Yes, it use the code from prior version. That's why I use set_transient() to mark that this plugin is just updated and use get_transient() after plugins_loaded hook (work when reloaded page) to check if it really is just updated then run the new code. The plugins_loaded hook will work after reload page and the transient will be check that plugin is just updated. Once new updated code had runned, it will be delete that transient so that it can run once after plugin updated.
    – vee
    Feb 14, 2022 at 7:46
  • 2
    Since this solution ultimately checks if the plugin was updated on plugins_loaded, it's much simpler to use the selected answer which does the same thing with much less code.
    – user54141
    Feb 15, 2022 at 14:19
  • @user54141 Yes, the accepted answer code is very good. I agreed. However this hook is still good for someone who may have better idea than I am to use it when upgrade plugin is done. This is what upgrader_process_complete hook is for.
    – vee
    Feb 15, 2022 at 15:28

From the discussion where they decided not to add a custom hook/function specific to upgrade, it sounds like "most people" (as of 4 years ago) use register_activation_hook, since it's called when a plugin is upgraded through the admin page; most examples I've seen since then follow that trend.

For most usage I would suggest not hooking through plugins_loaded, as it would get called on every page load. The exception to this is mentioned in the discussion: upgrade paths via FTP/SVN are 'edge cases', since WP wouldn't have a mechanism to know that the plugin was changed, in which case the previous answer might be more relevant.

See https://gist.github.com/zaus/c08288c68b7f487193d1 for a 'simple framework' example using register_activation_hook.

  • register_activation_hook is not guaranteed to be run on updates, see make.wordpress.org/core/2010/10/27/…
    – Flimm
    Sep 15, 2017 at 10:49
  • Very much - do NOT use plugins_loaded - runs every load, and can be burdensome / slow. Feb 28, 2019 at 23:17
  • register_activation_hook no longer runs on updates. That was an inconsistency they fixed. It is only triggered on plugin activations now. Using plugins_loaded is recommended in the codex. It's not slow because WP makes a single query for all of the site's options and caches it, so even though you're using get_option() to check the version saved in the DB, it doesn't actually make a DB request. It just checks the cached request.
    – user54141
    Oct 26, 2021 at 19:06

You can hook into the upgrader_pre_install and upgrader_post_install filters.


As I happen to be searching for something related in Nov. 2023, I thought I'd offer this suggestion: the update_plugins_{$hostname} filter was introduced in WordPress version 5.8.0.

This feature only works if the plugin is using the Update URI header. So if you're creating a plugin, you can include the Update URI header at the top of the main file with the value being your JSON's endpoint. Then you'd hook into the filter replacing {hostname} with the host name of that URL. For example, let's say this is my custom plugin file located in /wp-content/plugins/my-custom-plugin/my-custom-plugin.php and I've got the top here:

 * Plugin Name: My Custom Plugin
 * Version: 1.0.0
 * Author: AuRise Creative
 * Update URI: https://aurisecreative.com/update-my-custom-plugin/

Now I could hook into the plugin updates for it to retrieve my update data (if any) for that plugin.

 * Check for Plugin Updates
 * @param array|false $update The plugin update data with the latest details. Default false.
 * @param array $plugin_data Plugin headers.
 * @param string $plugin_file Plugin filename.
 * @param string $locales Installed locales to look up translations for.
 * @return array|false An associative array of update data on success. False otherwise.
function prefix_check_for_plugin_updates($update, $plugin_data, $plugin_file, $locales = '')
    // Do stuff
    return $update;
add_filter('update_plugins_aurisecreative.com', 'prefix_check_for_plugin_updates', 10, 4);

Unfortunately, this only works for plugins that use the header, so most likely in premium plugins.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.