frozennode/administrator
Frozennode Administrator is a Laravel admin package that generates configurable CRUD interfaces for Eloquent models, with built-in authentication, relationships, filters, sorting, and customizable fields and actions.
Install the package into a Laravel 4.x/5.0–5.2 project (it will not resolve on modern Laravel — see Gotchas):
composer require frozennode/administrator
Because it predates package auto-discovery, register it manually in config/app.php:
'providers' => [
// ...
'Frozennode\Administrator\AdministratorServiceProvider',
],
'aliases' => [
// ...
'Administrator' => 'Frozennode\Administrator\Facades\Administrator',
],
Publish the config and the front-end assets:
php artisan vendor:publish
# older Laravel 4:
# php artisan config:publish frozennode/administrator
# php artisan asset:publish frozennode/administrator
Set the essential values in config/administrator.php (or app/config/packages/frozennode/administrator/administrator.php on L4): title, login_path (where unauthenticated users are redirected), redirect_path, home_page, and the menu array. Point model_config_path at the directory that will hold one config file per model (config/administrator by default on L5).
Then write your first model config — the whole package is driven by these files:
// config/administrator/posts.php -> /admin/posts
return [
'title' => 'Posts',
'single' => 'post',
'model' => 'App\Post',
'columns' => ['id', 'title', 'created_at'],
'edit_fields' => [
'title' => ['type' => 'text'],
'body' => ['type' => 'wysiwyg'],
'published' => ['type' => 'bool'],
'author' => ['type' => 'relationship', 'name' => 'author', 'title' => 'Author'],
],
'sort' => ['column' => 'created_at', 'direction' => 'desc'],
];
Visit /admin and log in via your existing auth flow. The first real use case is exactly this: a small internal back office over a handful of Eloquent models where you want CRUD screens without writing controllers or Blade views.
Where to look first
config/administrator.php — global behavior (auth redirect, menu, dashboard, route middleware).vendor/frozennode/administrator/src/Frozennode/Administrator/Fields/ — the authoritative list of field types and their supported options (the README is thin on details).One config file per model, filename = route slug. users.php becomes /admin/users. Keep these in version control alongside your models. single => true turns a config into a singleton settings-style page with no list view.
Use columns and edit_fields as two separate projections. columns drives the index table (display names as keys, 'column'/'relation' keys for relations); edit_fields drives the create/edit form with type plus type-specific options. Don't try to make one do both.
query_filter for row-level scoping and eager loading. It receives the query builder and must return it:
'query_filter' => function ($query) {
if (! Auth::user()->isAdmin()) {
$query->where('user_id', Auth::id());
}
return $query->with('author');
},
action_permissions for per-record authorization. Keys are view, create, update, delete, and each can be a closure receiving the model. Pair this with the global permission closure so editors get a "no access" page rather than a broken form.
Field-type mapping you'll reach for constantly: text, textarea, wysiwyg, markdown, number, password, date, datetime, enum, bool, image, file, color, key, relationship. For relationships, 'name' => 'author' is the Eloquent method name; belongs_to/has_many/many_to_many options are inferred, but you can override with 'options' => function () { return User::pluck('name', 'id'); }.
formatter vs setter. formatter transforms a value for display in the list; setter transforms on save (function (&$model, $value) { $model->password = Hash::make($value); }). This is how you handle hashing, JSON packing, slug generation, and date normalization.
filters for the index view. Declare them explicitly with 'type' => 'date' / 'type' => 'select'; date filters also need date_format. Filters are the fastest way to make a large table usable without writing a custom query screen.
custom_actions for workflow buttons (approve, export, restore). The closure gets the model and can return a message. Actions appear as row buttons and POST to a generated route, so they inherit your middleware.
Menu grouping and dashboard. Global menu takes groups with title and items; home_page picks the model shown after login, and dashboard_view lets you render a custom dashboard Blade partial instead of a model list. This is the cheapest place to give non-technical users a landing page.
Override views, don't fork. Mirror the package view under your app's package-views directory (app/views/packages/frozennode/administrator/ on L4, resources/views/packages/... on L5) and the view finder prefers yours. Use this for branding, custom row markup, or extra buttons.
Protect the routes yourself. On L5.2+ set 'middleware' => 'auth' in the global config if your version supports it; otherwise wrap the generated route group in your own middleware. Always verify with php artisan route:list that admin/* routes are behind auth.
ArrayAccess signatures. Before committing, price up a maintained alternative (Filament, Nova, Backpack) or a fork you own — the config-file productivity gain evaporates if you're maintaining the package itself.login_path does not authenticate. It's only the redirect target for the login page. Without auth middleware the entire admin (including delete routes) is public. Add the middleware before you create the first model config.config/app.php is the most common first-run failure.php artisan config:clear. If model_config_path is wrong you get "no models" rather than an error. A model config whose model class doesn't autoload also tends to 404/blank rather than explain itself.posts.php to blog_posts.php changes the URL; there is no separate slug setting to fall back on.posts.created_at), and if it still misbehaves, fall back to query_filter ordering or an accessor column.public/packages/frozennode/administrator wasn't published. Image fields additionally need location (filesystem path) and name (public URL prefix), plus sizes for thumbnails.date_format. A mismatch saves 0000-00-00 instead of erroring. Use the PHP format string the picker expects (m/d/Y, Y-m-d H:i:s).password in columns; use a setter to hash and a 'visible' => false on edit fields where you want it omitted from the grid. Skipping the setter stores plaintext.unique validation needs care. The package's validation_rules are plain Laravel rules; on update, unique will flag the record against itself unless you use Rule::unique(...)->ignore($id) or a closure. messages in the model config customizes the copy.storage/logs/laravel.log; confirm the action name appears under custom_actions with the right permissions key.deleted_at column in columns confuses editors. Expose restore as a custom_action and keep the timestamp out of the grid.options and formatter closures execute on each page render — don't put unbounded queries in them; cache option lists if the table is large.vendor/. View overrides and custom Field subclasses (bind your own widget type) are the supported extension points; editing package source will be lost on the next composer update.How can I help you explore Laravel packages today?