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

printnow/laravel-admin

PrintNow Laravel Admin is a Laravel package for quickly building admin panels and back-office interfaces. It provides CRUD resources, authentication, menus, and role-based access, streamlining dashboards, data tables, and management workflows for developers.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a drop-in fork of Dcat Admin: the Composer package is printnow/laravel-admin, but all namespaces, config keys, and Artisan commands remain Dcat\Admin, config/admin.php, and admin:*. That means every Dcat tutorial and existing controller you find applies unchanged — you migrate by swapping the Composer requirement, not by rewriting code.

Install (~5 minutes)

composer require printnow/laravel-admin
php artisan admin:publish      # config/admin.php, app/Admin/, assets, migrations
php artisan admin:install      # runs migrations + seeds default admin user

Then log in at /admin/auth/login (default admin / admin — change immediately).

Where to look first

  1. config/admin.php — route prefix, auth guard, upload disk, menu, layout, extensions.
  2. app/Admin/routes.php — where your admin routes are registered.
  3. app/Admin/Controllers/HomeController.php — your dashboard entry point.
  4. app/Admin/bootstrap.php — global boot hooks (menus, extensions, Admin::script).

First use case — scaffold one CRUD screen

php artisan admin:make PostController --model=App\\Models\\Post

This generates a controller with grid(), form(), and detail() methods. Register it in app/Admin/routes.php:

use Illuminate\Routing\Router;
Admin::routes();

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

You now have a working list/create/edit/delete screen. Everything else in this package is a refinement of that one pattern.

Implementation Patterns

Grid — list screens. Build declaratively; column modifiers are chainable and lazy.

protected function grid()
{
    return Grid::make(new Post(), function (Grid $grid) {
        $grid->model()->with(['author'])->orderByDesc('id');

        $grid->column('id')->sortable();
        $grid->column('title')->limit(40)->filter();  // quick search
        $grid->column('author.name');                  // eager-loaded relation
        $grid->column('status')->using([1 => 'Draft', 2 => 'Live'])->label();
        $grid->column('cover')->image('', 60, 60);
        $grid->column('created_at');

        $grid->filter(function (Grid\Filter $filter) {
            $filter->equal('status')->select([1 => 'Draft', 2 => 'Live']);
            $filter->between('created_at')->datetime();
        });

        $grid->actions(function (Grid\Displayers\Actions $actions) {
            $actions->disableDelete();
            $actions->append(new PublishAction());   // custom row action
        });

        $grid->batchActions(function ($batch) { $batch->add(new BulkArchive()); });
        $grid->exporter(new PostExporter());
    });
}

Form — create/edit screens. The same closure runs for both; use $form->isCreating() / isEditing() for divergence. Hooks are the extension point, not overridden methods.

protected function form()
{
    return Form::make(new Post(), function (Form $form) {
        $form->display('id');
        $form->text('title')->required()->maxLength(120);
        $form->slug('slug')->creationRules(['required', 'unique:posts,slug']);
        $form->editor('body');
        $form->image('cover')->uniqueName()->autoUpload()->disk('public');
        $form->multipleImage('gallery')->sortable()->removable();
        $form->select('status')->options(Post::STATUSES)->default(1);
        $form->belongsTo('author_id', UserController::class);

        $form->saving(function (Form $form) {
            $form->slug = $form->slug ?: Str::slug($form->title);
        });

        $form->saved(function (Form $form, $result) {
            Cache::forget("post:{$form->getKey()}");
        });
    });
}

Detail — read-only view. Rarely needed if you have a good Grid, but cheap to produce:

protected function detail($id)
{
    return Show::make($id, new Post(), function (Show $show) {
        $show->field('title');
        $show->field('body')->unescape();
        $show->field('created_at');
    });
}

Custom pages. Skip the CRUD triad entirely for dashboards and reports:

public function index(Content $content)
{
    return $content
        ->title('Overview')
        ->body(new RevenueChart())         // any Dcat\Admin\Widgets\* or Blade view
        ->row(function ($row) {
            $row->column(4, new InfoBox('Orders', 'shopping-cart', 'blue', Order::count()));
            $row->column(4, new InfoBox('Revenue', 'money', 'green', Order::sum('total')));
        });
}

Permission & menu workflow. Menus are database rows, not code. Add routes in app/Admin/routes.php, then create the corresponding menu entry and role permissions at /admin/auth/menu and /admin/auth/roles — or seed them from database/seeders for repeatable environments. Route-level enforcement is the admin.permission middleware, which is already included in config('admin.route.middleware').

Extending with custom Actions. For any repeated operation (approve, sync, export), generate with php artisan admin:action PublishAction --grid-row and implement handle(). Row actions and batch actions are the cleanest way to add business logic without bloating controllers.

Integration tips

  • Use app/Admin/bootstrap.php for cross-cutting concerns: register menu items programmatically, add global JS/CSS via Admin::script() / Admin::style(), or extend the navbar.
  • Any custom API endpoint consumed by admin JS should live outside the admin middleware group (or add X-CSRF-TOKEN headers) to avoid the guard redirecting to login.
  • Keep business logic in services/models; admin controllers should only describe the UI. This is the single biggest maintainability lever in Dcat-style projects.

Gotchas and Tips

Package identity is deliberately confusing. You install printnow/laravel-admin but work entirely with Dcat\Admin\*. Do not rename namespaces or config keys — the fork exists precisely so you can switch requirements without touching application code. If you have the original dcat/laravel-admin installed alongside this one, Composer will conflict on the Dcat\Admin namespace; remove the old requirement explicitly.

Version pinning is your safety net. The star count is low (46) and the fork's Laravel 13 / PHP 8.5 support is forward-looking. Pin the exact version in composer.json and read the changelog before bumping. Diff against upstream Dcat when something behaves unexpectedly — most forks carry a small delta, and the issue is often upstream, not here.

Asset publishing is required after upgrades. php artisan admin:publish (add --force to overwrite) must follow every composer update; otherwise you get blank pages or broken JS with no error in laravel.log.

admin:install is not idempotent. It runs migrations and seeds the default user. On an existing database, run php artisan admin:publish and then php artisan migrate manually, and review the migration names first — the admin tables (admin_users, admin_roles, admin_permissions, admin_menu, admin_operation_log) are prefixed and won't collide, but you may not want them in a shared schema.

Config and menu caching. After editing config/admin.php, run php artisan config:clear. Menus are stored in the admin_menu table and cached; clear with php artisan admin:menu regenerations or by editing through the UI. Stale menus are a common "why isn't my page showing" cause.

Permission middleware will 403 silently on new routes. New routes need a corresponding menu/permission row, or they won't appear and direct access will be denied. When debugging a blank admin section, always check the admin_menu and admin_permissions rows before the controller.

Pjax and custom JavaScript. Pages are loaded via Pjax, so $(document).ready() fires only on the first load. Register scripts with Admin::script() (it runs per page load) or bind on pjax:end. If a page misbehaves, disabling Pjax for it (check your version for Admin::disablePjax()) is the fastest way to isolate the cause.

Uploads. config/admin.php exposes upload.disk and upload.directory. If images 404, the two usual causes are a missing php artisan storage:link, or the public disk being private in config/filesystems.php. ->disk('public') on the field overrides the default per-field.

Admin::user() is null outside HTTP requests. Guard it in Artisan commands, queued jobs, and scheduled tasks — Admin::user()?->id rather than chained property access.

N+1 in Grids is the top performance trap. $grid->column('author.name') will lazy-load per row unless you add $grid->model()->with(['author']). Similarly, $grid->column('comments_count') needs ->withCount('comments') on the model, not a closure.

Soft deletes need explicit handling. Add $grid->model()->withTrashed() and a filter for trashed if you want to see and restore deleted records; otherwise they vanish from the list with no UI affordance.

Debugging checklist. php artisan route:list (confirm the route exists in the admin group), php artisan config:clear (after config changes), storage/logs/laravel.log (server-side errors), the browser Network tab (Pjax/AJAX responses), and APP_DEBUG=true locally to surface the actual exception instead of the friendly error page.

Extension points worth knowing. Admin::navbar() / Admin::menu() for chrome, Widgets\* for reusable panels, custom Grid\Tools\AbstractTool for toolbar buttons, and Form::* field extensions via Form\Field subclasses for domain-specific inputs. Reach for these before overriding framework internals — upgrades stay cheap that way.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route