Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Theme Laravel Package

igaster/laravel-theme

Add multi-theme support to Laravel apps with igaster/laravel-theme. Organize views, assets, config, and translations per theme, switch themes at runtime, and fall back gracefully to default views. Simple integration for building customizable, themeable Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

igaster/laravel-theme adds a theme layer on top of Laravel's view finder: each theme gets its own views, assets, and optional parent theme, and view('home') resolves to the active theme first, then up the inheritance chain, then to your global resources/views.

Install and wire up

composer require igaster/laravel-theme
php artisan vendor:publish --provider="Igaster\LaravelTheme\ThemeServiceProvider"

Package discovery handles the service provider on modern Laravel; if you're on an older skeleton, register Igaster\LaravelTheme\ThemeServiceProvider and alias the facade ('Theme' => Igaster\LaravelTheme\Facades\Theme::class) in config/app.php.

Look here first, in this order

  1. config/theme.php — where themes are discovered from (paths), the default/fallback theme, and asset URL settings.
  2. A theme directory, e.g. resources/views/themes/default/ containing theme.json (name, extends, views-path, asset-path, settings) plus assets/.
  3. The Theme facade — Theme::set(), get(), current(), all(), exists(), find() is the entire day-to-day API surface.

Smallest working use case

// app/Providers/AppServiceProvider.php boot()
Theme::set('default');
{{-- resources/views/themes/default/home.blade.php --}}
@extends('layouts.app')   {{-- resolved inside the theme first --}}
@section('content')
  <link rel="stylesheet" href="{{ theme_asset('css/app.css') }}">
  <h1>Hello from the theme</h1>
@endsection

Then Route::get('/', fn () => view('home'));. If you see the themed output, everything else is configuration.

Implementation Patterns

Per-request theme selection via middleware. Don't hardcode Theme::set() in a service provider for anything beyond a default. Resolve the theme from the request — subdomain, session, user preference, or a tenants.theme column — in a middleware registered on the web group so it runs before any view is composed.

class ResolveTheme
{
    public function handle($request, Closure $next)
    {
        $theme = $request->user()?->theme
            ?? optional(Tenant::findByHost($request->getHost()))->theme
            ?? config('theme.default');

        Theme::set(Theme::exists($theme) ? $theme : config('theme.default'));

        return $next($request);
    }
}

Parent/child inheritance instead of copy-paste. Put your shared layouts, partials, and components in a base theme and have real themes declare "extends": "base" in theme.json. Each child only ships the views it actually overrides. This is the package's strongest feature — a "seasonal skin" theme can be three Blade files.

Theme-relative assets, not asset(). Use theme_asset('css/app.css') anywhere a theme file is referenced, and the package's @css / @js Blade directives inside theme layouts. asset() will happily point at a file that doesn't exist for the active theme — silent 404s that only show up in staging.

Route groups with a themed admin/marketing split. Apply the middleware to route groups (->middleware('theme:marketing')) so a single app serves a themed public site while /admin stays on your stock views.

Testing. Theme::set('mytheme') in setUp() (or a Pest beforeEach) and assert against rendered output; then a second test case flipping the theme proves no view is accidentally resolved from the wrong place. Keep a test that hits a missing-theme-view path to confirm fallback behaviour is what you expect.

Deploys. Theme views are ordinary Blade files under the view cache, so php artisan view:clear in your deploy script covers renames and theme.json edits. Clear it in dev constantly — stale compiled views are the #1 source of "my change didn't apply."

Gotchas and Tips

@js / @css collided with Laravel 9+ — fixed in v2.0.24. Laravel 9 introduced its own @js($data) directive for passing PHP values to JavaScript. Earlier releases of this package registered @js and @css as their own asset-directive names, which produced compile errors or silently broke Laravel's native @js. If you are on Laravel 9/10/11 and previously worked around this by renaming the directives, defining your own, or skipping the theme directives entirely, revert that workaround after upgrading and re-verify both usages still compile: the package's @js('js/app.js') and Laravel's @js(['user' => $user]).

PHP 8.2+ deprecations fixed in v2.0.24. Older 2.0.x versions triggered Creation of dynamic property ... is deprecated notices on PHP 8.2/8.3. Upgrade is a patch-level bump with no API changes, but run your view-rendering tests — a deprecation promoted to an exception by your error handler will surface as a blank page or 500.

Silent fallback to global views. Because the resolver falls back to resources/views, a view you forgot to copy into the theme renders from the non-themed directory instead of erroring. When "the theme isn't applying," don't debug the middleware first — grep for the view filename in both trees and confirm the themed copy exists and is spelled identically (Linux is case-sensitive; macOS hid this during development).

Exception, mail, and notification views bypass your middleware. Error pages render outside the normal web stack, and mailables/notifications render in queue workers where no HTTP middleware ran. Any themed layout used by these must set the theme explicitly or accept the global fallback. A Theme::set() that never ran gives you an unthemed 500 page — exactly when you least want one.

Middleware ordering matters more than you think. Anything that renders a view (exception handler, response macros, view composers registered on *) can execute before your ResolveTheme middleware if it's registered late. Put it at the top of the web group.

theme_asset() and your build tool coexist awkwardly. Vite/Mix writes to public/build; theme_asset() resolves into the active theme's asset-path. Pick one: either emit theme assets into per-theme public folders and use theme_asset(), or keep Vite output global and use asset() for the build while reserving theme_asset() for theme-specific images/fonts. Mixing them is how you get a production 404 on CSS only.

Cache and config quirks. Theme metadata from theme.json is read from disk; long-running Octane/Swoole workers will hold a stale discovery result until the worker reloads. If you're on Octane, clear caches and restart workers as part of deploy, not just view:clear.

Facade alias collisions. Aliasing Theme globally is fine until another package claims the name. Prefer the fully-qualified facade or inject the underlying theme manager where you can.

Extension points worth knowing. Theme::all() / exists() for an admin theme-picker UI, and extends for inheritance, are stable and safe to build on. Anything reaching into the resolver internals is not — keep a couple of feature tests around view resolution so a future patch release (like this one) can be adopted without archaeology.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib