Building the Loader Addon
Why a separate addon
A theme folder cannot execute PHP. The logic needed to inject CSS — checking the active theme, respecting the ignore_theme flag, serving the file — requires PHP. A companion loader addon handles all of this. The theme and the loader are distributed together as two separate zips.
Folder layout
addons/my-theme-loader/
├── addon.json
└── addon.phpaddon.json
{
"slug": "my-theme-loader",
"name": "My Theme Loader",
"version": "1.0.0",
"description": "Injects the My Theme stylesheet. Requires the My Theme theme to be installed and active."
}addon.php
Hooks::on('wordcore_loaded', function () {
$settings = Storage::get('core/settings', []);
$activeTheme = $settings['active_theme'] ?? '';
$installedThemes = Storage::get('core/themes', []);
$themeInstalled = isset($installedThemes['my-theme']);
$themeActive = $activeTheme === 'my-theme';
if ($themeActive) {
// Respect the Pages addon ignore_theme flag
$requestUri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
$base = WordCore::base();
$relativePath = $base
? preg_replace('#^' . preg_quote($base, '#') . '#', '', $requestUri)
: $requestUri;
$relativePath = '/' . ltrim($relativePath, '/');
$ignoreTheme = false;
foreach (Storage::get('pages/pages', []) as $page) {
$slug = '/' . ltrim($page['slug'] ?? '', '/');
if ($slug === $relativePath && !empty($page['ignore_theme'])) {
$ignoreTheme = true;
break;
}
}
if (!$ignoreTheme) {
Hooks::on('head', function () {
$file = WC_ROOT . '/themes/my-theme/style.css';
if (file_exists($file)) {
echo '<style>' . file_get_contents($file) . '</style>';
}
});
}
} else {
// Warn on the front end if theme is missing or inactive
$msg = !$themeInstalled
? 'My Theme Loader: the My Theme theme is not installed. Install it or remove this addon.'
: 'My Theme Loader: the My Theme theme is not active. Activate it or remove this addon.';
Hooks::on('head', function () use ($msg) {
echo '<script>
document.addEventListener("DOMContentLoaded", function () {
var b = document.createElement("div");
b.style.cssText = "position:fixed;top:0;left:0;right:0;z-index:99999;background:#c44240;color:#fff;font-family:Arial,sans-serif;font-size:13px;font-weight:600;padding:10px 16px;text-align:center;";
b.textContent = ' . json_encode($msg) . ';
document.body.prepend(b);
});
</script>';
});
}
});
Why inline the CSS rather than linking it
The themes/ directory is not publicly accessible — a direct <link> tag pointing into it will return a 403. Routing the file through a public WordCore route works, but causes a flash of unstyled content because the browser has to make a second HTTP request after the page has already rendered. Reading the file with file_get_contents() and echoing it as an inline <style> block delivers the CSS in the initial HTML response with no extra requests and no flash.