open-admin-org/open-admin
Open Admin is a Laravel admin panel and CRUD scaffolding package for building data-driven backends quickly. It includes model grids, forms, filters, actions, authentication, menus, widgets, and extensible components for managing models with minimal code, plus role-based access, media tools, and flex...
open-admin-org/open-admin is a drop-in-ish admin panel for Laravel, forked from z-song/laravel-admin but stripped of jQuery and rebuilt on Bootstrap 5 + vanilla JS. The public API (Grid, Form, Show, Tree, Actions, Admin facade) is intentionally close to laravel-admin, so most existing documentation and Stack Overflow answers for "laravel-admin" still translate — minus the jQuery-specific bits.
Minimum setup
composer require open-admin-org/open-admin
php artisan admin:install # publishes config, runs migrations, seeds admin user + menu
php artisan storage:link # if you plan to use file/image uploads
After install, log in at /{admin_prefix}/auth/login (default credentials are admin / admin — change them immediately).
Where to look first
config/admin.php — route prefix, auth guard, database connection, upload disks, middleware stack, extensions. This is the file you will revisit most.app/Admin/routes.php — admin routes are registered here, not in routes/web.php.app/Admin/bootstrap.php — global bootstrapping for the admin app: registering extensions, injecting assets, tweaking navigation.app/Admin/Controllers/ — every generated controller (HomeController, AuthController, your CRUD controllers) lives here.database/migrations/ published by admin:install — admin_users, admin_menu, admin_permissions, admin_roles, admin_operation_log, etc.First use case: scaffold one model
php artisan admin:make PostController --model=App\\Models\\Post
Then edit app/Admin/Controllers/PostController.php and fill in grid() and form():
protected function grid()
{
$grid = new Grid(new Post);
$grid->column('id')->sortable();
$grid->column('title')->filter('like');
$grid->column('published_at')->sortable();
return $grid;
}
protected function form()
{
$form = new Form(new Post);
$form->text('title')->rules('required|max:255');
$form->textarea('body');
$form->datetime('published_at');
return $form;
}
Add the route in app/Admin/routes.php ($router->resource('posts', PostController::class);) and the CRUD UI is live. That is your first "this package pays for itself" moment.
Grid is the workhorse. Treat grid() as the place where you decide list-page performance and usability:
$grid->model()->with(['author', 'category'])->where('status', 'published');
$grid->column('author.name'); // eager loaded, no N+1
$grid->column('title')->display(fn($t) => Str::limit($t, 60));
$grid->column('status')->using([0 => 'Draft', 1 => 'Live'])->label();
$grid->filter(fn($f) => $f->like('title'));
$grid->actions(fn($a) => $a->disableDelete()->disableEdit());
Common additions: $grid->exporter(new PostExporter), $grid->quickSearch('title'), $grid->disableRowSelector(), $grid->paginate(50).
Forms follow the same field-builder idiom. The most-used patterns:
$form->text('email')->rules('required|email|unique:users,email').$form->saving(fn(Form $f) => $f->model()->slug = Str::slug($f->title));$form->saved(fn($form) => ...) and $form->submitted(...).$form->select('author_id')->options(User::pluck('name','id')), $form->multipleSelect('tags'), $form->hasMany(...).$form->tab(...), $form->step(...), $form->row(...) to break long forms into blocks.Custom actions (RowActions, BatchActions, Actions) are where admin panels earn their keep. Put business logic in an Action class, not a controller — they are self-contained, reusable, and can render their own modal/drawer:
$grid->actions(fn(Actions $a) => $a->add(new PublishPost()));
Dashboard + widgets. Override HomeController::index() to compose Box, InfoBox, Chart, Card, Table widgets. Add global CSS/JS via Admin::css() / Admin::js() / Admin::script() inside app/Admin/bootstrap.php or per-controller.
Auth, roles, and audit. Roles and permissions are database-backed (admin_roles, admin_permissions, admin_role_permissions, admin_role_users). Bind permissions to routes in the admin UI, then check in code with Admin::user()->can('posts.edit') or Admin::user()->inRoles(['editor']). Enable config('admin.enable_operation_log') if you need an audit trail of model writes — useful but noisy, so scope it per-controller.
Extensions ecosystem. laravel-admin-compatible extensions typically ship a ServiceProvider that calls things like Admin::extend() / Admin::import(). Because open-admin kept the Encore\Admin namespace (check composer.json for the exact namespace on your pinned version), many of these still load — but any extension that touches jQuery will need a rewrite. Prefer extending by writing your own Action/Widget classes over pulling in old extensions.
Upgrade/migration workflow. Since this is a fork of a previously maintained package, pin a version in composer.json, run php artisan admin:publish --force after upgrades only if you have not customized the assets, and keep config/admin.php under version control so config drift is visible in diffs.
The jQuery removal is the #1 compatibility trap. Third-party laravel-admin extensions, custom Admin::script() snippets, .datepicker(), .select2(), .sortable() calls — none of these work unless the fork or your own code replaced them. Search the package's resources/ for the vanilla-JS replacements before assuming a feature is missing. When porting a snippet, translate $(el).on('click', ...) to el.addEventListener('click', ...) and jQuery-AJAX to fetch/axios.
Bootstrap 5 class renames will silently break custom views. If you have any Blade templates or view overrides, rewrite float-right → float-end, ml-* → ms-*, pl-* → ps-*, data-toggle → data-bs-toggle, data-target → data-bs-target. Old markup won't error; it'll just render wrong, which is harder to spot.
config/admin.php is cached. If php artisan config:cache has run, edits to route prefix, middleware, or upload disks won't take effect until php artisan config:clear. Same with route:clear after adding a route in app/Admin/routes.php.
Menu is database-driven, not file-driven. Adding a route doesn't add a sidebar link. Either use the admin UI's Menu section, or add the entry to app/Admin/bootstrap.php via Admin::menu(). After moving between environments, php artisan admin:import is the usual fix for an empty/mismatched menu.
Watch N+1 in grids. $grid->column('author.name') without $grid->model()->with('author') fires a query per row. On a 100-row page that's 100 queries. Always eager-load relations you display or filter on, and be aware that ->display(fn() => ...) callbacks execute per-row too.
Uploads need three things aligned: a valid storage (or S3) disk in config/filesystems.php, the corresponding upload config block in config/admin.php, and the public/storage symlink. Missing any of the three yields confusing "file saved but broken image" behavior.
Operation logging can balloon your DB. If enable_operation_log is true, every admin write is logged with a serialized payload. Fine for compliance, painful for high-traffic admin panels — disable globally and enable per-controller/model where required, or rotate the table.
Laravel version support is the biggest real-world risk. Because the upstream was archived and this is a fork with a modest community, the package's Laravel compatibility is narrower than what composer.json might suggest in practice. Before a Laravel major upgrade, check the fork's releases and test the admin login, one Grid, one Form, and one upload flow — those four cover the usual breakage points.
Debugging admin issues is mostly "look in the right place." For 404s: check config('admin.route.prefix') and app/Admin/routes.php. For 403s: roles/permissions tables and Admin::user()->can(...). For blank grids: PHP error swallowed by the response layer — check storage/logs/laravel.log first, then config('app.debug'). For stuck assets: php artisan admin:publish --force and hard-refresh with cache disabled.
When in doubt, search upstream docs. Errors and configuration keys are overwhelmingly shared with laravel-admin; searching for the upstream term returns many more results than searching for "open-admin". Just filter the answers mentally — anything involving jQuery, Select2, or old Bootstrap classes needs translation to this fork.
How can I help you explore Laravel packages today?