“The Theme & Plugin Seatbelt” – Locking Clients Out of Breaking Things
If you hand off WordPress sites to clients, you know the move: “I just disabled a plugin to test something… now the site is blank.” “I switched themes and lost the whole layout.” WordPress is very democratic: if a user has activate_plugins or switch_themes, they can nuke your setup in two clicks. Two tiny filters

If you hand off WordPress sites to clients, you know the move:
- “I just disabled a plugin to test something… now the site is blank.”
- “I switched themes and lost the whole layout.”
WordPress is very democratic: if a user has activate_plugins or switch_themes, they can nuke your setup in two clicks.
Two tiny filters give you a hard seatbelt:
// 1) Force-enable a critical plugin
add_filter('option_active_plugins', function ($plugins) {
return array_unique(array_merge($plugins, [
'my-critical-plugin/plugin.php',
]));
});
// 2) Lock allowed themes to a single one (multisite)
add_filter('allowed_themes', function () {
return ['my-theme']; // stylesheet slug
});
Let’s unpack what these actually do and where to put them.
1. Forcing a Plugin to Stay Active
What option_active_plugins does
WordPress stores the list of active plugins in the active_plugins option. Every time it loads plugins, it calls get_option( 'active_plugins' ). That call is filterable via option_active_plugins. (ACF Support)
So this:
add_filter('option_active_plugins', function ($plugins) {
return array_unique(array_merge($plugins, [
'my-critical-plugin/plugin.php',
]));
});
means:
- Take the array WordPress thinks is active,
- Append
my-critical-plugin/plugin.php, - De-duplicate it with
array_unique, - Hand that back to core.
Result: even if the option in the DB doesn’t contain your plugin (because the client “deactivated” it), by the time WordPress uses it, your plugin has been put back into the list.
Where to put this
Put the filter in a must-use plugin (recommended):
- Create
/wp-content/mu-plugins/lock-critical-plugin.php - Drop this inside:
<?php
/*
Plugin Name: Lock Critical Plugin
*/
add_filter('option_active_plugins', function ($plugins) {
return array_unique(array_merge($plugins, [
'my-critical-plugin/plugin.php',
]));
});
Must-use plugins load automatically and can’t be disabled from the admin UI. That’s the whole point.
Don’t put this in the theme – if someone switches the theme, the “lock” disappears.
What the client sees
- In Plugins → Installed Plugins, your critical plugin will always appear as “Active”.
- Clicking “Deactivate” will update the DB, but on the next page load the filter puts it back into the active list.
- From their perspective it “can’t be turned off”.
You should obviously tell them you’re doing this – it’s a safety feature, not a secret trap.
2. Preventing Theme Switching with allowed_themes
On multisite, WordPress has the concept of “network-allowed” themes. Internally, WP_Theme::get_allowed_on_network() loads the allowedthemes site option and then passes it through the allowed_themes filter. (Simon Fraser University)
Filter signature (simplified):
/**
* @param string[] $allowed_themes Array of theme stylesheet names.
*/
apply_filters( 'allowed_themes', $allowed_themes );
So this:
add_filter('allowed_themes', function () {
return ['my-theme']; // stylesheet slug, e.g. 'twentytwentyfive'
});
does:
- Ignore whatever is stored in
allowedthemes, - Tell WordPress: “the only theme allowed on this network is
my-theme.”
Effect in practice
On a multisite network:
- Only themes whose stylesheet slug is in that array are considered “allowed”.
- In Network Admin → Themes, only that theme will be available for sites.
- Site admins won’t be able to switch to any other theme, because nothing else is “allowed”.
Important details:
- The value in the array is the stylesheet slug (usually the theme folder name), not the display name.
- This hook is aimed at network / multisite theme management. On a single-site install it usually doesn’t come into play; there you’d rather use
pre_option_template/pre_option_stylesheetif you want to go full dictatorship.
Where to put this
Again: mu-plugin is best, so nobody can disable it.
<?php
/*
Plugin Name: Lock Network Theme
*/
add_filter('allowed_themes', function () {
return ['my-theme']; // e.g. 'samplehq-theme'
});
If you want different themes per site in a network, you’d use site_allowed_themes instead and return different arrays based on $blog_id, but that’s another article. (Simon Fraser University)
When to use these “seatbelts”
Use this pattern when:
- You’re maintaining client SaaS/admin dashboards on top of WordPress.
- There’s one theme and one or a few plugins that are absolutely non-negotiable.
- You want to reduce “I broke it by clicking around” tickets to zero.
But:
- Be transparent with clients (“These are locked on purpose; they power the app.”).
- Keep the mu-plugin in your Git repo so deployments are consistent.
- Only lock what is truly critical – don’t turn the whole site into a prison.
If you want, next step we can:
-
Wrap both patterns into a reusable “Safety Belt” mu-plugin with a config array:
$locked_plugins = ['my-critical-plugin/plugin.php', 'another/plugin.php']; $locked_themes = ['my-theme']; -
And I’ll help you write a short LinkedIn post around “How I stop clients from accidentally deleting their SaaS with one line of PHP.”
Bojan