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 Admin Laravel Package

dcat-x/laravel-admin

Laravel Admin is a full-featured admin panel toolkit for Laravel, offering CRUD scaffolding, form and grid builders, widgets, authentication, and permission management. It helps developers rapidly build data-driven backends with minimal code and a rich, extensible UI.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and boot (10 minutes)

composer require dcat-x/laravel-admin
php artisan admin:publish        # config/admin.php + app/Admin skeleton + assets
php artisan admin:install         # migrations, seeds admin_users, creates dirs

admin:install is a one-time, destructive bootstrap (creates tables, seeds admin/admin). Never re-run it on an existing environment — use admin:publish plus your own migrations instead.

Then confirm three files before writing any code:

  1. config/admin.php — route.prefix (default admin), route.namespace (App\Admin\Controllers), route.middleware, auth.guard/auth.provider, database.connection, upload.disk.
  2. app/Admin/routes.php — every controller must be registered inside the Admin::routes() group or it will 404 / lose the admin guard.
  3. app/Admin/bootstrap.php — global extension registration, menu/asset hooks.

First real use case: a CRUD screen for an existing model

php artisan admin:make UserController --model=App\\Models\\User

This writes app/Admin/Controllers/UserController.php with a working index(Content $content), create(), edit(), show() and a Grid/Form scaffold. Register it:

Route::group([
    'prefix'     => config('admin.route.prefix'),
    'namespace'  => config('admin.route.namespace'),
    'middleware' => config('admin.route.middleware'),
], function (Router $router) {
    $router->resource('users', 'UserController');
});

Log in at /admin, then iterate on the generated grid() and form() methods — that loop is 80% of day-to-day work with this package. Log in as admin/admin (change it immediately), add the menu entry in the admin UI (System → Menu), and assign permissions from there.

Look first at a generated controller, then at config/admin.php, then at the upstream dcat-admin documentation site — this repo is a thin fork, so the API docs live upstream even though the vendor name differs.

Implementation Patterns

Grid (list screens). The grid is a query builder plus a column renderer. Always preload relations on the model, not inside closures:

protected function grid()
{
    return Grid::make(User::with(['roles', 'team']), function (Grid $grid) {
        $grid->column('id')->sortable();
        $grid->column('avatar')->image('', 40, 40);
        $grid->column('name')->strong()->filter('like');
        $grid->column('roles.name')->label();          // relation column
        $grid->column('status')->using([0 => 'Off', 1 => 'On'])->badge();
        $grid->column('is_verified')->switch();        // inline edit
        $grid->column('created_at')->sortable()->filter('range')->sortable();

        $grid->quickSearch('name', 'email');
        $grid->filter(function (Grid\Filter $filter) {
            $filter->equal('team_id')->select(Team::pluck('name', 'id'));
            $filter->between('created_at')->datetime();
        });
        $grid->paginate(20);
        $grid->disableDeleteButton();
        $grid->export();
    });
}

Rules of thumb: display() closures are for formatting only (they run per-row and are not query-aware); heavy logic belongs in the repository or model accessors. Use $grid->model()->with(...) / ->where(...) for scoping, and ->as() + ->expand() / ->modal() to drill into related data without building a second controller.

Form (create/edit screens). Field declarations drive validation, persistence and casting:

protected function form()
{
    return Form::make(new UserRepository(), function (Form $form) {
        $form->display('id');
        $form->text('name')->required()->rules('max:120');
        $form->email('email')->required()->creationRules('unique:users,email');
        $form->password('password')->min(8)->saving(fn ($v) => $v ? bcrypt($v) : $v);
        $form->image('avatar')->disk('public')->uniqueName()->autoUpload();
        $form->select('team_id')->options(Team::pluck('name', 'id'))->default(1);
        $form->belongsTo('roles', RoleController::class);   // related form in a modal
        $form->hasMany('addresses', function (Form\NestedForm $form) {
            $form->text('city');
        });
        $form->divider();
        $form->switch('status')->default(1);
        $form->saved(function (Form $form) { /* after persist */ });
        $form->submitted(function (Form $form) { /* before validate/persist */ });
    });
}

Hook chain to remember: submitted → saving → saved, plus editing/deleting/deleted. Validation lives in rules(), creationRules(), updateRules() — not in $this->validate().

Repositories. EloquentRepository is the default. For non-Eloquent sources (API, ES, legacy DB), implement Dcat\Admin\Repositories\Repository (or extend QueryRepository) so Grid::make(new ApiRepository()) and Form::make(new ApiRepository()) still work. This is the cleanest extension seam; it keeps controllers tiny and is testable without HTTP.

Actions. Three flavours, all extend Dcat\Admin\Actions\*:

  • RowAction — per-row button ($grid->actions(fn (Actions $a) => $a->add(new ResendInvoice()))).
  • Action — toolbar button, often opening a modal form: set $this->form() / $this->handle() and $this->dialog().
  • BatchAction — bulk operations over selected keys.

Keep the handle() logic in a service class and let the action be a thin adapter; actions are awkward to unit test directly, services are not.

Layout, dashboards and widgets. Controllers extend Dcat\Admin\Layout\Content:

public function index(Content $content)
{
    return $content
        ->title('Dashboard')
        ->body($this->metrics());     // $content->row() / ->column(6) for grids
}

Metrics (Metrics\Card, Line, Bar, Doughnut, Trend, Progress) are the fastest path to a dashboard; Widgets\Card / Box / Table cover static panels. Wrap custom Blade in admin::layouts.content or render via Admin::view().

Permissions and menus. Menus and RBAC live in DB tables (admin_menu, admin_permissions, admin_roles), managed through the UI. In code, gate with ->permission('users.edit') on grid columns/actions and Admin::user()->can('...') / Admin::user()->inRole(). Remember menus are cached — after seeding/importing them, clear the cache.

Testing harness. Use Dcat\Admin\Tests\TestCase (Orchestra Testbench based) as the pattern for your own admin tests, and assert with assertSame, not assertEquals. Prefer named protected methods over inline closures in grid()/form() so you can call and assert them directly — this is also the repo's own convention (NullToStrictStringFuncCallArgRector, PHPStan level 5, Pint preset laravel). New admin code should pass composer pint/PHPStan cleanly if you intend to contribute back.

Gotchas and Tips

This is a low-adoption fork — verify before committing. 5 stars, 0 dependents, and a suspiciously future-dated release (2026-10-09). Before adoption: diff composer.json and the namespace against upstream dcat-admin/dcat-admin, pin an explicit version, and read CHANGELOG. Third-party extensions (dcat/laravel-admin-ext-*) expect the upstream package name — if the fork uses replace, extensions may resolve; if not, you'll fight composer. For anything long-lived, prefer upstream and treat this as a mirror.

Frontend/dist rules from the repo itself. resources/dist/ is compiled output — never edit it. After touching frontend sources rebuild with npx mix --production; production builds intentionally ship no sourcemaps, so stack traces from the browser will point at minified bundles. Layer modals must be opened with shade: [0.3, '#000'] — if you write custom JS that instantiates Layer without that option, modals render without the dimmed backdrop and can appear broken/uncloseable in some browsers.

declare(strict_types=1) + nullable form input is the #1 runtime trap. Optional fields and display()/saving() closures routinely hand you null, and the package's own rules (NullToStrictStringFuncCallArgRector) exist because of it. Cast explicitly — (string) $value — or give a ->default(''); never pass null into a string-typed helper.

Asset republishing eats your work. php artisan admin:publish --force overwrites published assets and config/admin.php / app/Admin stubs. Publish once, version-control config/admin.php and app/Admin, and re-publish only the specific tag (--tag=admin-assets).

Cache layers bite in this order: config:clear after editing config/admin.php (config may be cached in staging); route:clear after adding admin routes (route:cache breaks dynamically registered admin routes); menu/permission caches after RBAC changes (a "missing" menu button is almost always a stale cache or a missing ->permission() mapping, not a code bug).

N+1 and filters. display(function ($v) { return $this->relation()->count(); }) executes per row. Use with() + $grid->column('relation.field'), or a repository-level select/withCount. For filters, filter('like') on a non-indexed column is a silent table scan on large tables — use equal/between where possible.

Auth/guard mismatches. The admin panel uses its own guard (admin) and model, separate from web. Symptoms: login succeeds but you bounce back to /admin/auth/login, or Admin::user() is null inside a widget. Fix config/admin.php → auth.guard / auth.provider and the providers.admin entry in config/auth.php, then clear config cache.

File uploads. $form->image() writes to the configured upload.disk; if images 404, check php artisan storage:link and that config/filesystems.php exposes the disk publicly. Use ->uniqueName() to avoid collisions and ->autoUpload() for large forms.

Debugging admin failures. The UI is PJAX-driven: an exception inside a modal often renders a partial error page or nothing. Reproduce the request in a new tab (disable PJAX via the URL or dev tools) to see the real trace; check storage/logs/laravel.log first, then APP_DEBUG=true locally. For grid/form exceptions, wrap the closure in a try/catch temporarily and dump() — $content->body(...) swallows nothing, but modal endpoints do.

Extension points worth knowing. app/Admin/bootstrap.php (global behaviours, Admin::script(), Admin::style(), Admin::navbar()), Admin::routes() for custom public pages, config/admin.php → extensions + php artisan admin:import <name>, and custom RowActions for anything repeating. Traits mixed into controllers/actions should be guarded with method_exists($this, '...') before invocation — the repo's own compatibility rule, and it saves you on minor-version upgrades.

Strict types in your own app code. The package's declare(strict_types=1) convention applies to its source; if you copy its internals or contribute a fix, run composer pint and keep PHPStan at level 5 with no new errors, and use assertSame in tests. Inside your own app/Admin you can be looser, but matching the convention reduces surprises when you upgrade.

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.
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
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle