How to structure a mu-plugin with autoload in PHP in WordPress

Contents

Overview

This article explains in detail how to structure a must-use (mu-) plugin for WordPress that uses an autoloader in PHP. It covers mu-plugin loading rules, recommended file layout, several autoload strategies (PSR-4 simple autoloader, classmap caching, Composer integration), a safe bootstrap/loader pattern for mu-plugins, how to perform one-time setup (activation-like), and performance/security best practices. All code examples are ready to copy into files each example is shown in a runnable PHP form.

Why use an autoloader in an mu-plugin?

  • Organization: Keep code in a clean src/ tree with namespaces and many files without manual requires.
  • Performance: Autoloaders allow class-loading on-demand. Combined with a classmap or Composer-optimized autoload, this can be very fast.
  • Maintainability: PSR-4 style layout and namespaces make the code modular and testable.
  • Compatibility: Many modern libraries expect an autoloader (Composer).

Important mu-plugin facts (WordPress specifics)

  • All PHP files placed directly in wp-content/mu-plugins are automatically included by WordPress on each page load. Subdirectories are not autoloaded by core — WordPress will not descend into subfolders to include files automatically.
  • Therefore the typical pattern is a small loader file that sits in wp-content/mu-plugins and requires/includes a bootstrap file inside a subdirectory.
  • mu-plugins are loaded before regular plugins, and they do not run activation hooks (register_activation_hook wont be triggered). You must implement your own version-check or installation routine.

Design goals for the mu-plugin structure

  • Keep the root mu-plugins folder tidy — one small loader file.
  • Use namespaces and PSR-4-like layout for classes.
  • Provide a fallback autoloader if Composer is not present (useful for deploys where Composer was run locally or not available on the server).
  • Provide an optional optimized classmap to avoid file_exists/require overhead on every autoload call.
  • Provide a simple upgrade/run-once mechanism because register_activation_hook is not available.

Recommended file layout

Below is a common and recommended directory layout for a mu-plugin that uses a subdirectory and an autoloader.

Path Description
wp-content/mu-plugins/my-mu-plugin.php Bootstrap loader file that WordPress autoloads directly. Keeps the mu-plugins root minimal and includes the real bootstrap.
wp-content/mu-plugins/my-mu-plugin/bootstrap.php Main bootstrap and autoloader initialization file (inside subdir).
wp-content/mu-plugins/my-mu-plugin/src/ All PHP source files, PSR-4 namespace mapped, e.g. MyMuPluginFooBar
wp-content/mu-plugins/my-mu-plugin/vendor/ Optional Composer vendor directory (created when running composer install locally)
wp-content/mu-plugins/my-mu-plugin/classmap.php Optional generated classmap cache file that returns an array file> for fast class resolution.

Step 1 — Root mu-plugins loader file

Create a single, small file in wp-content/mu-plugins that delegates to your real bootstrap in a folder. This is required because WordPress doesnt automatically include PHP files in subfolders.


Step 2 — Main bootstrap and autoloader strategy

The bootstrap is responsible for:

  • Including Composers autoloader if available.
  • Falling back to a lightweight PSR-4 autoloader or a cached classmap.
  • Initializing the plugin (running one-time installation code if needed).

Composer-first bootstrap (recommended if you can run Composer)

If you use Composer, generate vendor/autoload.php inside the plugin folder and have your bootstrap require it. This is simple and performant (Composer can dump an optimized classmap).

init()
    }
} )

Note on namespaces and file paths

PSR-4 maps a namespace prefix to a directory. In the example above the namespace prefix is MyMuPlugin and maps to src/. A class MyMuPluginCoreBootstrap should be at src/Core/Bootstrap.php.

Step 3 — Example class (namespace structure)

 true,
            label  => My MU Post,
        ] )
    }

    // Add upgrade/run-once logic here if needed
}

Optional — Classmap caching (fast autoload)

For high-performance environments, avoid checking the filesystem for every autoload. Generate a classmap (array mapping fully-qualified class names to absolute file paths) once during deployment or via a small generator script. Then require classmap.php in the bootstrap and autoload using the array lookup (fast).

Example of a small classmap autoloader that looks for classmap.php first, then falls back to PSR-4 scanning:


Example classmap.php format

 /full/path/to/file.php, ... ]

return array(
    MyMuPluginCoreBootstrap => __DIR__ . /src/Core/Bootstrap.php,
    MyMuPluginSomeOther     => __DIR__ . /src/Some/Other.php,
    // ...
)

Generating the classmap

You can write a simple PHP script that scans the src/ tree, parses files for namespace/class declarations and writes out classmap.php. Or run Composer locally and use composer dump-autoload --optimize to get optimized classmaps in vendor/. If you prefer a custom generator, ensure the generator is run as part of your CI/deploy process to produce classmap.php.

Composer integration (preferred when possible)

If you can run Composer during development or as part of CI, the recommended flow is:

  1. Create composer.json inside the plugin folder (wp-content/mu-plugins/my-mu-plugin/composer.json) with an autoload PSR-4 mapping, e.g. MyMuPlugin: src/.
  2. Run composer install or composer dump-autoload -o locally or on CI. This creates vendor/autoload.php and optimized classmaps.
  3. Commit vendor/ or deploy vendor/ to the server, or ensure your deploy copies composer artifacts. The bootstrap will include vendor/autoload.php if present.
{
  name: yourname/my-mu-plugin,
  autoload: {
    psr-4: {
      MyMuPlugin: src/
    }
  },
  require: {}
}
# run in mu-plugin folder
composer install --no-dev
composer dump-autoload -o
// bootstrap will automatically require vendor/autoload.php (see earlier bootstrapping example)

One-time setup and version management (activation alternative)

Because mu-plugins dont have activation hooks, implement an internal version check and run an upgrade/install routine when your plugins version changes. Store a version option in the database and compare it on load.

maybe_run_install()
}

protected function maybe_run_install() {
    installed = get_option( my_mu_plugin_version, null )
    current = 1.2.0 // bump this when you ship upgrades

    if ( installed !== current ) {
        this->run_install_routines( installed )
        update_option( my_mu_plugin_version, current )
    }
}

protected function run_install_routines( previous_version ) {
    if ( is_null( previous_version ) ) {
        // first install: create DB tables, default options, etc.
    } else {
        // handle upgrades based on previous_version
    }
}

Full combined bootstrap example (Composer if available, classmap fallback, version check)

init()
    }
} )

Security and hardening points

  • Never require arbitrary user input: Do not construct include paths from untrusted input.
  • Normalize paths: Use realpath and check the file is inside your plugin folder if accepting generated paths.
  • Least privileges: Dont expose internal files or allow direct access to PHP files from the browser. Use .htaccess denies if serving static directories from web roots, or ensure PHP files are safe to execute.
  • Data sanitization: If your plugin writes files (e.g., generates classmap.php), validate and sanitize names and file contents.

Performance considerations

  • Use Composer optimized autoload (classmap): composer dump-autoload -o produces a fast classmap and is preferred for production.
  • Avoid file_exists checks in tight loops: Use a cached classmap to map classes directly to files without probing the filesystem each time.
  • Opcode cache: Ensure PHP opcode cache is enabled on the server (OPcache) to reduce include overhead.
  • Defer expensive work: Dont do heavy work in the global scope of bootstrapped files — perform it in hooks (init or later) so WP can serve cached/REST requests faster.

Best practices checklist

  • Place only a lightweight loader file in wp-content/mu-plugins/ that includes your subdirectory bootstrap.
  • Prefer Composer during development: keep composer.json in the plugin folder and deploy vendor/ or equivalent artifacts.
  • If Composer is not available on the server, include a classmap.php generated during CI/deploy and have the bootstrap require it for fast lookups.
  • Use PSR-4 namespace-to-directory layout (e.g., MyMuPlugin => src/).
  • Implement a version check stored in the database to run install/upgrade steps because activation hooks are not triggered for mu-plugins.
  • Hook initialization to plugins_loaded or init rather than doing heavy work at file include time.
  • Validate paths and prefer realpath checks when constructing includes to avoid directory traversal issues.

Troubleshooting common issues

  1. Classes not found: Verify namespace, class name, and file path match your autoloader mapping. For PSR-4 the FQCN minus prefix should match the relative path under src/ (namespace separators -> directory separators) and end with .php.
  2. Autoload not running in mu-plugin context: Ensure the root mu-plugins loader file is present in wp-content/mu-plugins and that it requires your bootstrap.php — subdirectory files are not included automatically by WordPress.
  3. Performance problems: Ensure Composer optimized autoload or classmap is deployed. Avoid producing classmap on first-run on every page load.
  4. Activation tasks not executed: Remember mu-plugins never trigger register_activation_hook implement a version check and run install code on init if option differs.

Summary

Structuring a mu-plugin with an autoloader provides a clean, modular, and maintainable way to ship code in WordPress. The correct pattern is a small root loader in wp-content/mu-plugins pointing at a subdirectory bootstrap that initializes an autoloader (Composer preferred, classmap or PSR-4 fallback otherwise). Implement a version-check-based installation/upgrade flow because activation hooks are not available for mu-plugins. Follow performance and security guidelines: use optimized classmaps, OPCache, normalize/verify paths, and avoid heavy processing at include-time.

Further reading



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

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