WordPress has three places where PHP code runs on every request: the core itself, the active theme, and plugins. But there is a fourth location most developers never think about: the wp-content/mu-plugins/ directory. Files placed here are called must-use plugins. They load automatically, before regular plugins and before the active theme, on every single page request — no activation step required, no way for an administrator to accidentally deactivate them. For agencies and developers building client sites, mu-plugins solve a recurring frustration: functionality that must always be present gets disabled, moved, or broken when a client pokes around the Plugins screen. Critical rewrite rules vanish. Security headers stop being sent. CPTs disappear. Must-use plugins make that category of incident impossible. The tradeoff is that they live outside the standard WordPress update workflow and can only be changed by physically editing or removing the file — which is why they work best for small, focused, version-controlled utilities rather than large feature plugins. Common use cases include: site-wide constants, XML-RPC disabling, security header injection, network-wide multisite configuration, and ensuring CPT/taxonomy registrations survive theme switches. This article walks through the mechanics, shows a practical example, and covers the one gotcha that trips everyone up on first use: the directory only auto-loads top-level PHP files, not files inside subdirectories.
Problem: Functionality that must always run on the site — security headers, environment constants, custom post type registrations — can be broken or disabled when someone deactivates the wrong plugin from the WordPress admin, or when a theme switch removes CPT code from functions.php.
Solution: Place the code in a PHP file inside wp-content/mu-plugins/. WordPress loads every top-level PHP file in that directory automatically, before regular plugins, with no activation step required and no way to deactivate it from the admin UI.
A minimal mu-plugin that defines site-wide constants — create wp-content/mu-plugins/site-constants.php:
<?php
/**
* Plugin Name: Site Constants
* Description: Defines environment constants loaded before any theme or plugin.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
define( 'MY_THEME_VERSION', '2.1.0' );
define( 'MY_API_BASE_URL', 'https://api.example.com/v1' );
define( 'MY_SUPPORT_EMAIL', 'support@example.com' );
WordPress only auto-loads PHP files at the top level of mu-plugins/. If you organise code in subdirectories, create a single loader file at the top level that manually require_onces the files you need:
<?php
/**
* Plugin Name: MU Plugin Loader
* Description: Loads must-use plugins from subdirectories.
*/
require_once WPMU_PLUGIN_DIR . '/disable-xml-rpc/disable-xml-rpc.php';
require_once WPMU_PLUGIN_DIR . '/security-headers/security-headers.php';
require_once WPMU_PLUGIN_DIR . '/custom-post-types/register-cpts.php';
Other common mu-plugin patterns: disable XML-RPC with a single line, or send security headers on every response:
<?php
/**
* Plugin Name: Disable XML-RPC
*/
add_filter( 'xmlrpc_enabled', '__return_false' );
/**
* Plugin Name: Security Headers
*/
add_action( 'send_headers', function () {
header( 'X-Frame-Options: SAMEORIGIN' );
header( 'X-Content-Type-Options: nosniff' );
header( 'Referrer-Policy: strict-origin-when-cross-origin' );
} );
NOTE: Must-use plugins are listed in Plugins → Must-Use in the WordPress admin, but they cannot be activated or deactivated from there. They are also invisible to the standard WordPress plugin update checker — keep them in version control and deploy changes through your normal deployment workflow rather than editing files directly on the server.