Every WordPress site accumulates snippets: disable a feature, change an email address, add a shortcode. They are almost always pasted into the theme's functions.php, which is the worst of the available options.
Why functions.php is wrong
Two reasons, and both eventually happen.
It belongs to the theme. Change themes and every snippet is gone: usually discovered weeks later as a set of unrelated things that stopped working.
A syntax error takes the site down. The theme loads on every request, so a missing bracket produces a white screen with no dashboard to fix it from. Recovery means the file system.
A child theme's functions file fixes neither problem; it is still a theme file. Creating a WordPress child theme walks through what child themes are genuinely for, which is templates and styling.
A site-specific plugin
This should be the default. Create wp-content/plugins/site-specific/site-specific.php:
<?php /** * Plugin Name: Site Specific Customisations * Description: Custom code for this site. * Version: 1.0 */ // Custom code below.
Activate it like any plugin. Everything it contains now survives theme changes, appears in the plugin list where the next person will find it, and can be deactivated from the dashboard if it causes trouble.
That last point is the practical one. A snippet in a plugin that breaks something is switched off in two clicks; the same snippet in a theme file is a file-system rescue.
Must-use plugins
Files placed directly in wp-content/mu-plugins/ load automatically, before ordinary plugins, and cannot be deactivated from the dashboard.
Create the directory if it does not exist. One caveat catches everyone: only files directly in that directory load. A file inside a subdirectory is ignored, which is why a plugin dropped in as a folder appears to do nothing.
This is the right place for code that must never be switched off; a security restriction on a client site, a setting enforced across a network, something that must run before other plugins do.
It is the wrong place for anything experimental, because the recovery route is the file system in place of the dashboard.
Choosing between them
One question: should someone be able to turn this off from the dashboard?
Yes; a site-specific plugin. That covers most customisations, and being able to disable it is a feature in place of a weakness.
No, must-use. Enforced policy, network-wide behaviour, anything that would be a problem if a client deactivated it while experimenting.
Loading order
Must-use plugins load first, then ordinary plugins alphabetically, then the theme's functions file.
This matters when code needs to run before something else registers. It also means a must-use plugin cannot rely on a normal plugin having loaded yet: code that calls a plugin's function directly at load time fails, and the fix is to hook it later rather than to move the file.
Keep snippets separate
Rather than one growing file, use several small ones in mu-plugins with clear names, or clearly commented blocks in the site-specific plugin.
Each block should say what it does and why it exists. Six months later, an unlabelled snippet is code nobody dares remove and nobody understands, which is how sites accumulate customisations that outlive their reasons.
Editing safely
Do not use the built-in file editor for this. It saves directly to a live site, and a syntax error is immediate.
Edit locally or over SFTP, keep a copy of the previous version, and test on staging when the change is not trivial. Setting up a staging site walks through that, and securing WordPress goes into disabling the editor entirely, which is worth doing on any site with more than one administrator.
Know which of your code is running
Snippets accumulate in several places at once, and the first task on an unfamiliar site is producing the list.
ls -la wp-content/mu-plugins/ 2>/dev/null wp plugin list --status=active --field=name wp eval 'echo get_stylesheet_directory() . "/functions.php\n";' grep -c . "$(wp eval 'echo get_stylesheet_directory();')/functions.php"
Must-use plugins do not appear in the ordinary plugin list on some screens, which is exactly why code placed there gets forgotten. A file dropped into that directory years ago is still running and nobody remembers adding it.
Read all three locations before adding anything. Half the time the function you are about to write already exists somewhere in them, and the other half you are about to conflict with it.
Namespace everything you add
Two snippets defining a function with the same name produce an immediate fatal error, and generic names are how it happens.
function ahx_disable_emojis() { /* ... */ }
add_action('init', 'ahx_disable_emojis');
if (!function_exists('ahx_helper')) {
function ahx_helper() { /* ... */ }
}
A short prefix on every function, class and option name removes the entire class of problem. It also makes the code searchable later, since one string finds everything you added.
The existence check is worth using for anything that might be defined elsewhere, because it converts a site that stops working into a site where one feature quietly does not load, which is recoverable.
Test before it reaches the front page
A syntax error in a must-use plugin takes the whole site down, including the administration screen you would use to remove it.
php -l wp-content/mu-plugins/ahx-site.php wp eval 'echo "wordpress loaded\n";'
Check the syntax before uploading and confirm the site still loads afterwards. Keep access to the files by another route, since recovering from this needs a file manager or a shell rather than the administration screen.
Where a change is more than a few lines, make it on a copy first. The recovery from a broken must-use plugin is simple only if you can reach the files. Making a development copy of a site in cPanel goes into the copy.
Write down why each piece exists
The problem with a snippets file is not the code. It is that in two years nobody knows whether a given block is still needed.
/** * Disable XML-RPC. Added 2026-08-20. * Reason: repeated brute force attempts against xmlrpc.php. * Remove if: a mobile app or Jetpack needs it. */
Three lines per block: what it does, why it was added, and what would make it removable. That last line is the one that lets somebody delete it confidently later.
Without it, snippets are never removed, because nobody can establish what would break. A file of forty undocumented additions is a permanent liability that grows, and every one of them runs on every request.