TL;DR:
> mv wp-content/themes/foo wp-content/themes-private/foo > wp shell wp> update_option( 'template_root', '/themes-private' ); wp> update_option( 'stylesheet_root', '/themes-private' ); wp> delete_site_transient( 'theme_roots' );
Background
WordPress allows you to have multiple theme directories inside your content folder, via register_theme_directory()
. Having multiple theme folders is great, because it lets you organize large codebases better, pull themes from multiple version control repositories (without a mess of externals/submodules), etc.
Every registered directory has a corresponding theme root, which is just its relative path to the content folder. For example, if you have the default /home/foo/public_html/wp-content/themes
folder, and then register /home/foo/public_html/wp-content/themes-private
as an additional folder, then the theme roots will be /themes
and /themes-private
.
Problems Moving Between Theme Roots
As great as having multiple theme directories is, you may[1] run into a problem if you try to move an active theme from one root to another, in the form of this nasty fatal when loading the front end:
The theme directory "{theme slug}" does not exist.
This happens because locate_template()
relies on TEMPLATEPATH
and STYLESHEETPATH
, which are ultimately based on get_option( 'template_root' )
and get_option( 'stylesheet_root' )
. Those are only set/updated when switching themes, so they’re stuck at the old values until you fix them.
The theme_roots
site transient also maps theme slugs to theme roots, and needs to be refreshed.
The Solution
Fixing them is as simple as calling update_option()
and delete_site_transient()
from WP-CLI or an mu-plugin:
> mv wp-content/themes/foo wp-content/themes-private/foo > wp shell wp> update_option( 'template_root', '/themes-private' ); wp> update_option( 'stylesheet_root', '/themes-private' ); wp> delete_site_transient( 'theme_roots' );
[1] – Because the options are only set when switching themes, you may not run into the problem at all.
[2] – You can also delete them instead of updating them, but I haven’t looked into potential side-effects of that.