I've built my own plugin, hooking into a combination of

  • site_transient_update_plugins
  • transient_update_plugins
  • plugins_api

to automatically query, along with passing the current plugin version to a specified endpoint, which in return, grabs a temporary URL zip file from an S3 bucket based on the version provided.

This basically allows me to manage a lot of plugin versions per specific WordPress environments without pushing my private code WordPress. This all works fantastic. My endpoint after being fed ?v=1.0.0 will return a URL to a .zip file from our S3 bucket with the next 2.0.0 release, like so:


Unzipping plugin-name.zip produces a single directory of plugin-name, which contains the 100% correct version files. No problem there.

WordPress correctly tells me that my plugin has an update. I can see the custom details I provided from my endpoint such as description, screenshots, changelog, version, tested, required, banners, etc.

However, when I go to upgrade the version in WordPress, all works good, downloads and installs a new version (even refreshes the version from 1.0.0 to 2.0.0), but once I reload/navigate to/from the page, it errors with:

The plugin plugin-name/plugin-name.php has been deactivated due to an error: Plugin file does not exist.

I look in the wp-content/plugins and find my plugin has been renamed from plugin-name to plugin-name-<random-slug>. <random-slug> is always different, but it ALWAYS contains the new versions code (2.0.0 in my case).

I can re-activate the plugin, but now if I run wp plugin list from the CLI, I get plugin-name-<random-slug>, and in my code, I explicitly rely on the correct plugin-name/plugin-name.zip

This is my first real time doing anything like this in WordPress, so a little unsure how to fix. How can I hook into the upgrade, so after it's downloaded the zip, and unpacked it, I can rename the folder back to plugin-name?

Any ideas would be much appreciated!

add_filter('site_transient_update_plugins', array($this, 'register_update_check'));
add_filter('transient_update_plugins', array($this, 'register_update_check'));
add_filter('plugins_api', array($this, 'register_plugin_details_overrides'), 20, 3);


public function register_update_check($updates)
    if (! is_object($updates)){
        return $updates;

    if (! isset($updates->response ) || ! is_array($updates->response)) {
        $updates->response = array();

    // Query WordPress plugins available
    $this->response = $this->queryPluginVersions();

    // Compare the version
    // If returned version is greater than installed version,
    // mock & return WordPress response, feeding it a .zip
    // file which WordPress downloads, unzips the zip,
    // completely replacing the plugin and its files
    if ($this->response->version > $this->version) {

        // Only mock our plugin
        $updates->response['plugin-name/plugin-name.php'] = (object) array(
            'slug'         => 'plugin-name',
            'new_version'  => $this->response->version,
            'url'          => $this->response->url,
            'package'      => $this->response->download_url,
            'sections'     => array(
                'description'   => $this->response->sections->description,
                'installation'  => $this->response->sections->installation,
                'changelog'     => $this->response->sections->changelog,
                'screenshots'     => $this->response->sections->screenshots,

    return $updates;


public function register_plugin_details_overrides($result, $action, $args)
    if ($action !== 'plugin_information') {
        return $result;

    if ('plugin-name' !== $args->slug) {
        return $result;

    return (object) json_decode(json_encode($this->response), true);


public function queryPluginVersions()
    // Build the query, appending ?v=<version>
    $url = sprintf(

    $remote = wp_remote_get($url, array(
        'timeout' => 10,
        'headers' => array(
            'Accept' => 'application/json'

    return json_decode($remote['body']);


Just imagine endpoint URL returns correct information, with a valid & correct v2.0.0 .zip/release

  • 1
    WP tracks which plugin is active based on the file path of the activated plugin, the bug looks to be in your extraction code, it needs to remove and replace the existing folder, but since you didn't include that code your question can't be answered at the moment. Please edit your question to include that part. It could even be that your release process itself generates zips containing those folders with the extra parts in their name, and that's where the issue is, not the plugin
    – Tom J Nowell
    Commented Dec 14, 2018 at 23:11
  • I had not thought of that, makes sense! I have edited my question with the code if that makes it easier!
    – Kingsley
    Commented Dec 14, 2018 at 23:51
  • To confirm, a fresh download of the same .zip file WordPress download does not include any other characters other than plugin-name, so it is definitely WordPress doing it along the way
    – Kingsley
    Commented Dec 14, 2018 at 23:57
  • What about the name of the zip itself?
    – Tom J Nowell
    Commented Dec 15, 2018 at 0:51
  • The name of the zip file is same name of plugin-name.zip, it’s the S3 bucket directory that contains the version and the zip name is the same throughout. Now I think of it, I need to zip it as a different name, so WP can unpack as something different (without conflict like there is now), delete the existing, and then rename the name back to plugin-name. Does that sound about right?
    – Kingsley
    Commented Dec 15, 2018 at 1:43

1 Answer 1


Thanks Tom J Nowell! That led me down the right direction and I've been able to update successfully. Here's what I did:

  1. Re-structured my repo to match:
|-- .git
|-- .gitattributes
|-- .gitignore
|-- README.md
|-- package.json
|-- plugin-name
    |-- plugin-name.php
    |-- src
        |-- ...
  1. Code new version in a new branch, merge into master

  2. From command line, cd into root of directory above, & run:

git archive -o <plugin-name>-<major>.<minor>.<patch>.zip --format=zip --prefix=<plugin-name>/ HEAD:<plugin-name>

  1. Upload the to S3 <bucket>/wp-plugin/v<plugin-major-version>/<zipfile>.zip, e.g.


  1. ???

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.