This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
AdminLTE 4 for Laravel (colorlibhq/adminlte-laravel) is the official Laravel package integration of AdminLTE 4 (Bootstrap 5.3, vanilla JS, no jQuery). It provides:
- Config-driven sidebar menu with permissions, active states, badges
- 40 Blade components (widgets, forms, charts, calendar, kanban, wizard, modals)
- Scaffolding system (
adminlte:scaffold) for 18 DB-backed app sections - Dependency-free RBAC (roles, permissions, middleware, permission-aware Gate)
- Auth scaffolding (
adminlte:make-auth) for plain/Breeze/Fortify - Plugin system for lazy-loading JS libraries (Flatpickr, Tom Select, Tabulator, Quill, ApexCharts, jsVectorMap, FullCalendar, SortableJS)
- i18n with 9 locales (en, de, es, fr, it, ja, pt_BR, ru, zh), RTL support
- Bundled demo pages and in-app docs (served at
/docs) - Vite-first asset pipeline
This is a Composer package, not a Laravel application. Requires PHP ^8.3 and Laravel (illuminate) ^13.0. Tests run against Orchestra Testbench — the package is never "run" directly; it's consumed via composer require colorlibhq/adminlte-laravel + php artisan adminlte:install.
composer test # PHPUnit
composer lint # Pint check only (what CI runs)
composer fix # Pint, applying fixes
composer analyse # PHPStan level 8 (bakes in --memory-limit=1G — the bare
# `vendor/bin/phpstan` exhausts PHP's default 128M limit)
composer check # lint + analyse + test (the full CI sequence)
# Run a single test class/method
vendor/bin/phpunit tests/SmokeTest.php
vendor/bin/phpunit tests/SmokeTest.php --filter testComponentsRenderWithoutErrorsCI (.github/workflows/tests.yml) runs on PHP 8.3 + 8.4 + 8.5 against Laravel 13 / Testbench 11, in this order: composer lint → composer analyse → composer test. All three must pass.
Single entry point. On boot it:
- Registers 40 Blade components under the
adminlte-prefix (e.g.<x-adminlte-card>), from the$componentsmap at the top of the class - Registers the
AdminLtemenu-builder singleton (aliasedadminlte) and thePluginManagersingleton - Registers
@pluginStyles/@pluginScriptsBlade directives — these emit PHP that runs at request time, so plugins enabled by components during render are reflected - Registers translations both under the
adminlte::namespace and as a default-namespace path, so views can use plain__('adminlte.key')without publishing - Registers demo routes (
/demo/*, toggle withconfig('adminlte.demo')) and in-app docs routes (/docs/{page}, renders the markdown indocs/with the AdminLTE layout) - Opportunistically wires RBAC for the consuming app: model policies,
role/permissionmiddleware aliases, and a permission-awareGate::before— all guarded byclass_existsonApp\...classes that only exist afteradminlte:scaffold rbac - Listens to auth events (Login/Logout/Failed) and writes them via
ActivityLogger, which no-ops when theactivity_logtable is absent
| Command | Purpose |
|---|---|
adminlte:install |
Publish config + Vite stubs, wire Vite, prompt for npm deps (--only=config|views|assets|lang) |
adminlte:scaffold {section} |
Publish a DB-backed section into the consuming app (--all, --force, --seed) |
adminlte:make-auth |
Auth controllers/routes (--type=plain|breeze|fortify) with hardening |
adminlte:status |
Show install state |
The largest subsystem. ScaffoldCommand holds a declarative $manifest mapping each of 18 sections (dashboard, mailbox, chat, kanban, calendar, projects, file-manager, profile, settings, invoice, pricing, faq, notifications, api, impersonation, activity-log, realtime, rbac) to what it publishes: migrations, models, factories, controllers, Form Requests, policies, seeders, feature tests, views, and route stubs. All stubs live in resources/stubs/ as .php.stub files organized by type (models/, controllers/, migrations/, policies/, rbac/, realtime/, etc.). To change what a section generates, edit both the manifest and the stub files.
AdminLteclass (singleton): builds and filters the sidebar/navbar menu fromconfig('adminlte.menu')- Menu items flow through a filter pipeline (
config('adminlte.filters')), in order:SearchFilter— normalizes menu dataGateFilter— filters by authorization (cankey; works with the RBACGate::before)HrefFilter— resolves routes to URLsActiveFilter— marks current page as active
- Singleton ensures runtime
addAfter()calls persist for the request (reset next request) - Scoped retrieval:
menu('sidebar'),menu('navbar-left'),menu('navbar-right') - The filter pipeline is the single source of truth for menu rendering logic
- Lazy-loads optional JS/CSS libraries; config under
config('adminlte.plugins')(flatpickr, tom_select, tabulator, quill, apexcharts, jsvectormap, fullcalendar, sortablejs) - Plugin-backed components call
app(PluginManager::class)->enable('plugin-name')in their constructor — rendering the component is what triggers asset loading renderStyles()/renderScripts()append a cache-busting?v=<filemtime>to asset URLs (skipped when the file isn't on disk, e.g. in tests)- Bundled default asset paths are patched into config entries that omit
css/jskeys
PHP classes: src/View/Components/{Form,Widget,Tool}/ → views: resources/views/components/{form,widget,tool}/ (namespaced adminlte::)
- Widget (21): Card, SmallBox, InfoBox, Alert, Callout, Progress, Timeline, ProgressGroup, DescriptionBlock, ProfileCard, Ratings, NavNotifications, NavMessages, NavTasks, DirectChat, Toast, Tabs/Tab, Accordion/AccordionItem, Breadcrumb
- Form (9): Input, InputSwitch, InputColor, InputFile, InputFlatpickr, InputTomSelect, Textarea, Select, Button
- Tool (10): Modal, Datatable, Editor, Chart, VectorMap, Calendar, Kanban, Sortable, Wizard/WizardStep
Pattern: constructor parameters become tag attributes; public helper methods (e.g. cardClass()) compute dynamic CSS/conditional content; render() returns the matching adminlte::components.* view. Plugin-backed components additionally call PluginManager::enable() in the constructor.
ActivityLogger— staticlog()into the scaffoldedactivity_logtable; silently no-ops if the table doesn't exist, so package code can call it unconditionallyNavbarData— feeds the navbar notification/message dropdowns from real DB tables when scaffolded, falling back to demo data otherwise
- Base:
tests/TestCase.phpextends Orchestra Testbench and registers onlyAdminLteServiceProvider - Files:
SmokeTest.php(component rendering, menu filtering, auth views),PluginSystemTest.php,WidgetComponentTest.php,ScaffoldCommandTest.php,MakeAuthCommandTest.php - Component tests render the namespaced view directly and assert on output strings:
$view = view('adminlte::components.widget.card', [...])->render();
$this->assertStringContainsString('card', $view);- Scaffold/auth command tests run the artisan command in Testbench and assert published files exist/contain expected content
- PHP class in
src/View/Components/{Category}/YourComponent.phpextendingIlluminate\View\Component - Blade view in
resources/views/components/{category}/your-component.blade.php - Register in the
$componentsarray inAdminLteServiceProvider('your-component' => ...→<x-adminlte-your-component>) - Test rendering in
tests/WidgetComponentTest.phpor similar
- Implement
FilterInterfaceinsrc/Menu/Filters/—transform(array $item): ?array, return null to drop the item - Append to
'filters'inconfig/adminlte.php(order matters)
- Add entry under
'plugins'inconfig/adminlte.php(enabled,css,js) - Create a component whose constructor calls
app(PluginManager::class)->enable('your-lib') - Test via
@pluginStyles/@pluginScriptsoutput inPluginSystemTest.php
- Add
.php.stubfiles under the matchingresources/stubs/subdirectories - Add the section to
$sectionsand$manifestinScaffoldCommand - Add a feature test in
tests/ScaffoldCommandTest.php - Document in
docs/scaffolding.md(docs are user-facing — served in-app at/docs)
Never hardcode users or App\Models\User in a stub. Apps rename the table
and move the model. Write {{ users_table }}, {{ users_model_import }} (after
use), or {{ users_model_ref }} (inline, before ::class) and RendersStubs
substitutes the resolved names (Support\UserTable) at publish time.
{{ users_model_ref }} is namespace-aware — it emits a bare short name only when
the stub already lives in the model's namespace, and fully qualifies it
otherwise. ScaffoldCommandTest::test_no_stub_hardcodes_the_users_table enforces
it; the primary key is deliberately not resolved (see docs/scaffolding.md).
- PHP: PSR-12 via Pint; strict types and explicit return types
- Static analysis: PHPStan level 8 via Larastan (
phpstan.neon;view('adminlte::...')view-string errors are intentionally ignored because PHPStan doesn't boot the provider) - Naming: camelCase methods, kebab-case Blade component tags and view names
- This is a package, not an app — never assume an
app/directory; consuming-app classes (App\Models\...) are referenced as strings and guarded withclass_exists - Menu is a singleton — runtime mutations persist per-request only
- Plugins are lazy — assets load only if a rendered component enables them
docs/is dual-purpose — GitHub docs and rendered in-app at/docs; keep markdown links relative (foo.md), the docs route rewrites them- Demo pages under
resources/views/demo/mirror the AdminLTE showcase and back the live demo at laravel.adminlte.io - All 9 locales must stay complete — when adding a translatable string, add the key to every file in
resources/lang/; verify withphp -r '$en=require "resources/lang/en/adminlte.php"; foreach(["de","es","fr","it","ja","pt_BR","ru","zh"] as $l) print $l.": ".count(array_diff_key($en, require "resources/lang/$l/adminlte.php")).PHP_EOL;'(all zeros) config('adminlte.logo')/ footer keys render unescaped ({!! !!}) by design — they must only ever hold trusted hardcoded markup; never route user input into them