php-panel/laravel-admin
A Laravel admin panel package for quickly building back-office interfaces with CRUD screens, authentication, and customizable resources. It helps developers scaffold admin dashboards while keeping control over models, forms, and UI, making routine management tasks faster to implement and easier to m...
php-panel/laravel-admin is a drop-in admin panel for Laravel: you register Eloquent models as "resources," and the package gives you listing, filtering, form editing, and delete flows without hand-writing controllers or Blade views.
Install and boot (5 commands):
composer require php-panel/laravel-admin # v3.1.0+
php artisan admin:install # publishes config, migrations, assets
php artisan migrate
php artisan admin:make-user # or seed your own admin user
Then visit /admin (the default route prefix) and log in.
Where to look first, in order:
config/admin.php (or whatever admin:install publishes) — this is the single most important file. Route prefix, middleware stack, auth guard, upload disk, and per-page pagination all live here.app/Admin/ (or your configured resource path) — generated resource classes live here; this is 90% of your day-to-day work.routes/ and src/Http/Controllers/ if you need to override a controller action; most customization happens in resources, not here.First meaningful use case: expose an existing model (say Post) as a managed resource.
php artisan admin:make PostResource --model=App\\Models\\Post
You get a resource class with a form() and a table() (or grid()) method. Fill those in with field definitions, register the resource in your admin service provider, and you have a working CRUD screen. Everything else in this assessment is about what happens after that first screen works.
Version note for this release: v3.1.0 is the Laravel 13 compatibility release. If you are on Laravel 12 or below, there is no functional reason to rush this upgrade — take it when you move the app. If you are moving to Laravel 13, this is the minimum version; do not attempt 3.0.x on Laravel 13.
Resource-per-model, one file each. The dominant pattern is one resource class per Eloquent model, registered centrally. Keep them thin — field definitions and query scoping only. Anything with real logic (computed columns, multi-step saves) belongs in the model, an action class, or a resource hook, not in the resource file.
Form/table symmetry. Define the same concepts in both form() and table(): a field that's editable in the form should be visible in the list, otherwise admins can't verify their own edits. A very common source of "the panel is broken" tickets is a column present in the form and absent from the table (or vice versa).
Query shaping per resource. Rather than editing the package's listing controller, scope the listing inside the resource — eager-load relationships, default sort, and a base query. This is where you solve N+1 problems on listing pages (see Gotchas).
Hooks over overrides. Most admin packages of this shape expose lifecycle hooks (beforeSave, afterSave, beforeDelete, form/table callbacks). Use them for: slug generation, audit logging, cache invalidation, sync of pivot data. Overriding controller actions works but ties you to internals that change between minor versions.
Authorization via Laravel policies. Don't build a bespoke permission layer on top of the panel. Register standard policies on your models; the panel resolves against Gate/authorize. This keeps rules identical between the admin panel and the rest of the app, and makes php artisan test cover both.
Separate guard for admin users. Configure the panel's guard in config rather than reusing the web guard with a role check. It cleanly separates session lifetimes, and avoids "logged out of the shop when logging out of admin" surprises.
Localization and theming. Publish views/assets only when you actually need to change them, and re-publish after minor upgrades — published views become yours to maintain and will silently drift from upstream fixes.
Testing. Feature-test resource screens with an authenticated admin user and assertSee on a known record. Keep a small smoke test per resource (list, create, edit, delete); it catches 80% of breakage from package upgrades, including the Laravel 13 session-serialization class of bug.
The Laravel 13 session-flash fix is a landmine if you wrapped it. v3.1.0 changes the session flash payload from an object to a plain array to satisfy Laravel 13's JSON session serialization. If you extended or subclassed any part of the panel that writes flash data (custom redirects after save, custom action handlers), audit those call sites: pass arrays, not objects or collections. Symptom on Laravel 13 is a serialization error or silently missing flash messages after a save — not an obvious exception pointing at your code.
Laravel 13 minimum is hard. Check composer.json constraints for php and illuminate/* before upgrading a production app; Laravel 13 bumps the PHP floor. Run the upgrade on a branch, clear bootstrap/cache/*.php and php artisan optimize:clear, and re-publish any config that gained new keys — admin:install will not overwrite an existing config by default, which is the correct behavior and also the reason your new-version defaults silently don't apply.
Route and middleware conflicts. The panel registers a catch-all under its prefix. If you have an existing /admin route group (health checks, legacy endpoints), you get confusing 404s that look like permission errors. Check php artisan route:list --path=admin first whenever "the panel 404s for one user."
Uploads and disks. File/image fields resolve against the configured disk, and the package stores the relative path, not a URL. When moving between local and s3, existing rows still contain local paths — plan a migration, not a config flip.
N+1 on listing pages. A table with relationship columns will issue a query per row unless you eager-load the relation on the resource's base query. This is invisible with 20 seed rows and painful with 20,000. Watch DB::listen or Laravel Debugbar on a listing page before shipping it.
Pagination and "why is my record missing." Default sorts matter: without an explicit deterministic sort, records with identical timestamps can shuffle between pages, and admins report disappearing rows. Always set a tiebreaker (created_at then id).
Permission caching. If you cache roles/permissions (or use a cached Gate), a newly granted permission won't appear until the cache clears. Add cache invalidation to your role-assignment flow, not to the panel.
Debugging order for "it doesn't work": (1) config cache cleared, (2) resource registered in the provider, (3) route listed, (4) policy returns true, (5) asset files published. That order matches how often each is the culprit.
Extension points worth knowing: the resource base class (override per-resource behavior), lifecycle hooks (cross-cutting concerns), and the config file (global defaults). Reach for published views last — they are the highest-maintenance extension point and the one most likely to break silently across minor releases.
Upgrade cadence for this package specifically: pin to the minor, not the patch. The 3.0.x → 3.1.0 jump shows the maintainers use minors for framework-support shifts, which are the releases most likely to change session/payload assumptions.
How can I help you explore Laravel packages today?