alizharb/laravel-themer
Enterprise-grade theme management for Laravel. Create, clone, activate, and safely delete themes with per-theme Vite builds, NPM workspaces, asset shortcuts, view overrides, and Livewire 4 support. Includes metadata, wizards, and fast production caching.
Explore advanced theming techniques and patterns for complex applications.
Laravel Themer dispatches events during theme lifecycle operations.
use AlizHarb\Themer\Events\ThemeActivated;
use AlizHarb\Themer\Events\ThemeDeactivated;
use AlizHarb\Themer\Events\ThemeRegistered;
In a Service Provider:
use Illuminate\Support\Facades\Event;
use AlizHarb\Themer\Events\ThemeActivated;
public function boot()
{
Event::listen(ThemeActivated::class, function ($event) {
\Log::info('Theme activated:', [
'theme' => $event->theme->name,
'version' => $event->theme->version,
]);
// Clear caches
\Artisan::call('cache:clear');
\Artisan::call('view:clear');
});
}
$event->theme; // Theme instance
In addition to Laravel events, laravel-themer supports executing automated system hooks via your theme.json file. This is extremely useful for running Artisan commands (like migrations or seeders) precisely when a theme is activated or deactivated.
theme.json{
"name": "E-Commerce Pro",
"slug": "ecommerce-pro",
"asset_path": "themes/ecommerce-pro",
"hooks": {
"after_activate": [
"php artisan db:seed --class=EcommerceProSeeder",
"npm run build --prefix themes/ecommerce-pro"
]
}
}
Whenever php artisan theme:activate ecommerce-pro runs, these array of shell/artisan commands will automatically fire.
Create advanced theme logic with service providers.
php artisan theme:make MyTheme --provider
<?php
namespace Theme\MyTheme;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\View;
class ThemeServiceProvider extends ServiceProvider
{
public function register(): void
{
// Register theme-specific services
$this->app->singleton('mytheme.settings', function () {
return [
'primary_color' => '#6366f1',
'font_family' => 'Inter',
];
});
}
public function boot(): void
{
// Register custom Blade directives
Blade::directive('theme_button', function ($expression) {
return "<?php echo view('theme::components.button', $expression); ?>";
});
// Share data with all views
View::share('themeSettings', app('mytheme.settings'));
// Register middleware
$this->app['router']->pushMiddlewareToGroup(
'web',
\Theme\MyTheme\Http\Middleware\ThemeMiddleware::class
);
}
}
Create middleware that only runs when a theme is active.
<?php
namespace Theme\MyTheme\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ThemeMiddleware
{
public function handle(Request $request, Closure $next)
{
if (!is_theme_active('mytheme')) {
return $next($request);
}
// Theme-specific logic
view()->share('darkMode', $request->cookie('dark_mode', false));
return $next($request);
}
}
Allow users to switch themes at runtime.
public function switchTheme(Request $request, string $theme)
{
$manager = app('themer');
if (!$manager->find($theme)) {
abort(404, 'Theme not found');
}
// Store in session
session(['active_theme' => $theme]);
// Or store in user preferences
$request->user()->update(['theme' => $theme]);
return redirect()->back();
}
public function handle(Request $request, Closure $next)
{
if ($user = $request->user()) {
$theme = $user->theme ?? config('themer.active');
app('themer')->setActiveTheme($theme);
}
return $next($request);
}
Different themes for different tenants.
// In a service provider
public function boot()
{
if ($tenant = tenant()) {
$theme = $tenant->theme ?? 'default';
app('themer')->setActiveTheme($theme);
}
}
class TenantThemeResolver
{
public function resolve(): string
{
$tenant = tenant();
return match($tenant->plan) {
'enterprise' => 'premium-theme',
'pro' => 'professional-theme',
default => 'basic-theme',
};
}
}
Create theme variants for different contexts.
{
"name": "corporate-dark",
"parent": "corporate",
"tags": ["dark-mode", "variant"]
}
public function getSeasonalTheme(): string
{
$month = now()->month;
return match(true) {
$month === 12 => 'holiday-theme',
$month >= 6 && $month <= 8 => 'summer-theme',
default => 'default-theme',
};
}
# Cache theme discovery
php artisan theme:cache
This creates bootstrap/cache/themes.php with all discovered themes.
[@if](https://github.com/if)(get_active_theme()->slug === 'premium')
[@vite](https://github.com/vite)(['resources/assets/css/premium.css'], 'themes/premium')
[@endif](https://github.com/endif)
<livewire:heavy-component lazy />
use Tests\TestCase;
class ThemeTest extends TestCase
{
public function test_theme_activation()
{
$this->artisan('theme:activate mytheme')
->assertSuccessful();
$this->assertEquals('mytheme', config('themer.active'));
}
public function test_theme_views_resolve()
{
app('themer')->setActiveTheme('mytheme');
$this->assertTrue(view()->exists('theme::welcome'));
}
}
public function test_theme_asset_helper()
{
app('themer')->setActiveTheme('mytheme');
$asset = theme_asset('logo.png');
$this->assertEquals('/themes/mytheme/logo.png', $asset);
}
Extend theme discovery for custom sources.
class DatabaseThemeLoader
{
public function load(): Collection
{
return DB::table('themes')
->where('active', true)
->get()
->map(function ($row) {
return new Theme(
name: $row->name,
slug: $row->slug,
path: storage_path("themes/{$row->slug}"),
version: $row->version,
);
});
}
}
class RemoteThemeLoader
{
public function load(): Collection
{
$response = Http::get('https://themes.example.com/api/themes');
return collect($response->json())->map(function ($data) {
// Download and extract theme
$this->downloadTheme($data['url'], $data['slug']);
return new Theme(
name: $data['name'],
slug: $data['slug'],
path: base_path("themes/{$data['slug']}"),
);
});
}
}
Build a theme marketplace for your application.
public function install(string $themeSlug)
{
// Download theme package
$package = Http::get("https://marketplace.example.com/themes/{$themeSlug}/download");
// Extract to themes directory
$zip = new ZipArchive;
$zip->open(storage_path("themes/{$themeSlug}.zip"));
$zip->extractTo(base_path("themes/{$themeSlug}"));
$zip->close();
// Run theme:check
Artisan::call('theme:check', ['theme' => $themeSlug]);
// Install dependencies
Artisan::call('theme:npm', ['theme' => $themeSlug, 'command' => 'install']);
}
Don't modify theme activation logic directly - use events.
Avoid hardcoded paths and environment-specific logic.
Use semantic versioning in theme.json.
List required packages in theme README.
Ensure child themes properly override parent resources.
How can I help you explore Laravel packages today?