maxcxam / larsadmin
An admin panel for Laravel, built with Inertia and Vue 3.
Requires
- php: ^8.3
- inertiajs/inertia-laravel: ^3.0
- laravel/framework: ^12.0|^13.0
- symfony/finder: ^7.2|^8.0
Requires (Dev)
- laravel/mcp: ^0.7.1|^0.8|^0.9|^1.0
- laravel/pint: ^1.20
- laravel/scout: ^11.0
- orchestra/testbench: ^10.0|^11.0
- pestphp/pest: ^4.0|^5.0
- pestphp/pest-plugin-laravel: ^4.0|^5.0
- symfony/yaml: ^7.2|^8.0
Suggests
- laravel/boost: Installs the larsadmin AI guidelines and skills for AI coding assistants (^2.0).
- laravel/mcp: Runs the local larsadmin MCP server for AI assistants via php artisan larsadmin:mcp (^0.7.1|^1.0, Laravel 12 needs 12.41.1+).
- laravel/scout: Searches models using Laravel\Scout\Searchable through their search engine in the global search (^11.0).
Provides
None
Conflicts
None
Replaces
None
README
An admin panel for Laravel, built with Inertia and Vue 3: describe each Eloquent model once as an Entity and get lists, forms, filters, actions, metrics and dashboards.
Website: larsadmin.dev · Documentation · Live demo
Features
- Entities: one class per model with fields, search (including
relation.column), sorting, pagination and row scoping (indexQuery,detailQuery,relatableQuery). - Fields:
ID,Text,Textarea,Number,Boolean,Date,DateTime,Password,Select(enums too), with per-context and per-record visibility, read-only rules and validation. - Files and images:
FileandImagefields, one file or several (multiple()), public and private disks including S3, signed re-authorized links for private files, deletion of replaced files. - Global search: a Ctrl/⌘+K command palette across the Entities that opt in, over their
$searchcolumns or through Laravel Scout, limited to what policies andindexQuery()let the user see. - Audit log (opt-in): who changed what through the panel, with "before → after" values per field,
written in the same transaction as the change; masking of sensitive fields, a history panel on each
record and an "Audit log" page behind its own gate, retention with
larsadmin:prune-audit. - Notifications and background actions (opt-in): a bell in the header with the user's panel
notifications, sent by your code with
LarsadminNotification; actions marked$queuedrun on the Laravel queue as the user who started them, with progress, cancelling, a result notification and owner-only file downloads (ActionResponse::file()); stale runs of a lost worker are failed. Polls by default, realtime through Reverb or Pusher when configured, with the client bundled (nothing to install). - Relations:
BelongsTo(searchable),HasManyandBelongsToManypanels with attach/detach. - Field blocks: group form and detail fields into panels (one or two columns, collapsible) and tabs.
- Filters: select, boolean and date range filters, plus your own.
- Actions: bulk, standalone and per-row ("⋯" menu) actions with modal fields (extension fields included), confirmation, batching and per-record availability.
- Metrics and dashboards: Value, Trend, Partition and Progress cards, scoped to what the user may see, on dashboards or above an entity list.
- Security by default: panel gate, model policies that fail closed, server-built record URLs.
- UI: GitHub-like interface with light, dark and system themes, column picker, responsive layout.
- i18n: Ukrainian (default) and English, with a language switcher.
- Extensions (public API since 1.5): Composer packages add their own Vue fields, dashboard cards
(static or with data from an authorized, cached endpoint), filters and sidebar pages, their own routes
and translations, and can override the UI of selected built-in components. Installed with
composer requirealone; fail closed when an extension is missing or broken. Scaffolds, a Vite preset and test helpers for authors; since 1.6 record abilities for fields (authorizedTo()), extension fields in action modals and the publicFileSafetyfor extensions that store or serve files. - AI assistants (since 1.6, opt-in): 15 skills and a guideline for Laravel Boost or
php artisan larsadmin:ai, and a local MCP server (php artisan larsadmin:mcp, needs the optionallaravel/mcp) with guide search, read-only metadata of your entities and the generators. Never runs in production. - Generators for entities, actions, filters, metrics, dashboards and extensions.
The frontend ships prebuilt: your application does not need Node.js, also with extensions installed.
Inspired by the developer experience of Laravel Nova: an Entity plays the role of a Nova Resource.
Requirements
- PHP 8.3+
- Laravel 12 or 13
Installation
composer require maxcxam/larsadmin php artisan larsadmin:install
larsadmin:install publishes config/larsadmin.php, the assets (public/vendor/larsadmin) and
app/Providers/LarsadminServiceProvider.php (registered in bootstrap/providers.php), publishes the
package migrations (audit log, background action runs), and copies lang/uk/validation.php if your app
does not have one yet. The audit log stays off until you run the migration and enable it (see the
audit log guide). Notifications and background actions are
off too (see the notifications guide).
The panel lives at /admin. After updating the package, refresh the assets:
composer update maxcxam/larsadmin php artisan larsadmin:publish
Quick start
An entity
php artisan larsadmin:entity Post --model=Post
Classes in app/Larsadmin are registered automatically.
namespace App\Larsadmin; use App\Larsadmin\Actions\Publish; use Illuminate\Http\Request; use Maxcxam\Larsadmin\Entity; use Maxcxam\Larsadmin\Fields\BelongsTo; use Maxcxam\Larsadmin\Fields\DateTime; use Maxcxam\Larsadmin\Fields\ID; use Maxcxam\Larsadmin\Fields\Select; use Maxcxam\Larsadmin\Fields\Text; use Maxcxam\Larsadmin\Fields\Textarea; use Maxcxam\Larsadmin\Filters\SelectFilter; class Post extends Entity { public static string $model = \App\Models\Post::class; public static string $title = 'title'; public static array $search = ['id', 'title']; public function fields(Request $request): array { return [ ID::make()->sortable(), Text::make('Title', 'title')->sortable()->rules('required', 'max:255'), BelongsTo::make('Author', 'author', User::class)->searchable()->nullable(), Select::make('Status', 'status')->rules('required')->options([ 'draft' => 'Draft', 'live' => 'Live', ]), Textarea::make('Body', 'body')->nullable(), DateTime::make('Published at', 'published_at')->nullable()->sortable(), ]; } public function filters(Request $request): array { return [ SelectFilter::make('Status', 'status')->withOptions(['draft' => 'Draft', 'live' => 'Live']), ]; } public function actions(Request $request): array { return [ Publish::make()->showInline(), // bulk menu, record page and the row "⋯" menu ]; } }
BelongsTo points at the related entity (App\Larsadmin\User here), not the model.
An action
php artisan larsadmin:action Publish
namespace App\Larsadmin\Actions; use Illuminate\Support\Collection; use Maxcxam\Larsadmin\Actions\Action; use Maxcxam\Larsadmin\Actions\ActionFields; use Maxcxam\Larsadmin\Actions\ActionResponse; class Publish extends Action { public function handle(ActionFields $fields, Collection $models): ?ActionResponse { $models->each->update(['status' => 'live', 'published_at' => now()]); return ActionResponse::message("Published: {$models->count()}"); } }
By default an action may run on the records the user can update; canRun() replaces that check.
A metric and a dashboard
php artisan larsadmin:value NewPosts php artisan larsadmin:dashboard Overview
namespace App\Larsadmin\Metrics; use App\Larsadmin\Post; use Maxcxam\Larsadmin\Metrics\MetricRequest; use Maxcxam\Larsadmin\Metrics\Results\ValueResult; use Maxcxam\Larsadmin\Metrics\Value; class NewPosts extends Value { public function calculate(MetricRequest $request): ValueResult { // An Entity class: counts only the rows the user may see (indexQuery + viewAny). return $this->count($request, Post::class)->allowZeroResult(); // show 0 as a number } }
namespace App\Larsadmin\Dashboards; use App\Larsadmin\Metrics\NewPosts; use Illuminate\Http\Request; use Maxcxam\Larsadmin\Dashboards\Dashboard; class Overview extends Dashboard { public function cards(Request $request): array { return [ NewPosts::make()->help('Posts created in the selected period.'), ]; } }
Register the dashboard in app/Providers/LarsadminServiceProvider.php; the first one the user may see
becomes the home page:
protected function dashboards(): array { return [ new \App\Larsadmin\Dashboards\Overview, ]; }
Metric helpers given an Entity class (or
$this->scoped(Post::class, fn ($query) => ...)) are scoped by the entity'sindexQuery()and needviewAny. A model class or a query builder is not scoped and aggregates every row it can reach: use it for global numbers only and hide the card withcanSee()when needed.
An extension
php artisan larsadmin:extension acme/larsadmin-rating composer config repositories.acme-larsadmin-rating path packages/acme/larsadmin-rating composer require acme/larsadmin-rating:@dev cd packages/acme/larsadmin-rating && npm install && npm run build
The package registers itself in its service provider and ships its built files:
Larsadmin::extension('acme/larsadmin-rating', __DIR__.'/../dist')->translations('acme-rating');
// resources/js/extension.js import { defineExtension } from 'larsadmin' import RatingInput from './fields/RatingInput.vue' import RatingDisplay from './fields/RatingDisplay.vue' export default defineExtension({ name: 'acme/larsadmin-rating', requires: '^1.6', fields: { 'acme/rating': { form: RatingInput, display: RatingDisplay } }, })
A field class with public string $component = 'acme/rating' is then used like any other field. Extension
code is trusted code that runs with the administrator's rights; the server still authorizes everything. See
the extensions guide.
AI assistants
The package ships skills and a guideline for AI coding assistants (Claude Code, Cursor and others) in
resources/boost/, and a local MCP server. Both are optional and meant for development.
# With Laravel Boost 2.x: picks up the larsadmin guideline and skills php artisan boost:install # Without Boost: copy the skills to .claude/skills and add the MCP server to .mcp.json composer require laravel/mcp --dev php artisan larsadmin:ai --skills --mcp
The larsadmin MCP server (php artisan larsadmin:mcp, stdio) offers search-docs, read-docs,
application-info, list-entities, describe-entity, list-dashboards, list-extensions and generate.
It returns metadata only (never record data or secrets) and writes files only through generate with
write: true. It runs only with laravel/mcp installed, by default only when APP_ENV=local
(LARSADMIN_MCP=true also allows other non-production environments) and never in production. See the
AI assistants guide.
Configuration
config/larsadmin.php:
| Key | Env | Default | Purpose |
|---|---|---|---|
name |
LARSADMIN_NAME |
APP_NAME + Admin |
Panel title |
domain |
LARSADMIN_DOMAIN |
null |
Serve the panel on a domain |
path |
LARSADMIN_PATH |
admin |
URI prefix |
guard |
LARSADMIN_GUARD |
null |
Authentication guard |
locale |
LARSADMIN_LOCALE |
uk |
Panel language (null keeps the app locale) |
locales |
uk, en |
Languages offered by the switcher | |
timezone |
LARSADMIN_TIMEZONE |
null (app.timezone) |
Timezone metric periods are aligned to |
metrics.cache_store |
null |
Cache store for metrics using cacheFor() |
|
middleware, auth_middleware |
Middleware of all / authenticated panel routes | ||
allow_without_policy |
true |
Allow models that have no policy | |
allow_local |
LARSADMIN_ALLOW_LOCAL |
true |
Skip the gate when APP_ENV=local |
actions.max_all |
1000 |
Limit for "run on all matching records" | |
actions.queued_max_all, actions.queued_max_active |
10000, 3 |
Background action limits: "all matching" records, active runs per user | |
actions.stale_after, actions.queued_stale_after |
3600, 86400 |
Seconds after which a running run without $timeout (no heartbeat, plus 60 s) or a queued run counts as stale and is failed |
|
actions.queue, actions.queue_connection |
LARSADMIN_QUEUE, LARSADMIN_QUEUE_CONNECTION |
null |
Default queue and connection of background actions |
search.* |
limit 5, min_length 2, throttle 60 |
Global search; see the search guide | |
audit.enabled |
LARSADMIN_AUDIT |
false |
Turn the audit log on (publish and run the migration first) |
audit.connection |
LARSADMIN_AUDIT_CONNECTION |
null |
Database connection of the audit log |
audit.* |
retention_days 90, store_ip / store_user_agent true, max_value_length 1000, per_page 25, throttle 60 |
Audit log; see the audit log guide | |
notifications.enabled |
LARSADMIN_NOTIFICATIONS |
false |
Turn on the bell and background actions (run the migrations first) |
notifications.realtime |
LARSADMIN_REALTIME |
auto |
Realtime through Reverb/Pusher: auto, true or false |
notifications.channel_prefix |
LARSADMIN_CHANNEL_PREFIX |
larsa_ |
Prefix of the panel's broadcast channels |
notifications.* |
poll_interval 60, per_page 15, retention_days 30, throttle 120 |
Notifications; see the notifications guide | |
extensions.source_maps |
LARSADMIN_EXTENSION_SOURCE_MAPS |
false |
Serve extensions' source maps (debugging only) |
extensions.max_age |
31536000 |
Browser cache lifetime of versioned extension files, in seconds | |
mcp.enabled |
LARSADMIN_MCP |
null |
Local MCP server: null only with APP_ENV=local, true any non-production environment, false off; never in production |
Authorization
-
Panel access. Outside
APP_ENV=local, only users passing theviewLarsadmingate get in. The gate denies everyone until you open it inapp/Providers/LarsadminServiceProvider.php:protected function gate(): void { Gate::define('viewLarsadmin', fn ($user) => in_array($user->email, ['admin@example.com'])); }
-
Local environment. While
APP_ENV=localandallow_localistrue, every authenticated user can open the panel. SetLARSADMIN_ALLOW_LOCAL=falseon any shared or public server. -
Policies fail closed. When a model has a policy, it decides everything (
viewAny,view,create,update,delete, attach/detach); a missing policy method means "deny". Models without a policy are allowed whileallow_without_policyistrue. -
Row scoping.
indexQuery()anddetailQuery()limit what a user sees; records outside them answer 404. -
Audit log. Reading the log needs the
viewLarsadminAuditgate, which the package never defines: nobody sees the log until you define it (allow_localdoes not apply). The global "Audit log" page is not row-scoped: a holder of the gate sees every entry of each Entity they haveviewAnyon, also outside theirindexQuery(). -
Background actions re-authorize everything in the queue worker as the user who started them and fail closed. Bell notifications and runs are visible to their owner only; the panel's realtime channels are authorized only by the panel's own endpoint.
Localization
The panel speaks Ukrainian (default) and English. Users switch the language in the header; you can
also resolve it yourself with Larsadmin::resolveLocaleUsing(). Publish the translations to change
them:
php artisan vendor:publish --tag=larsadmin-lang
Theming
Light, dark and system themes, switchable in the header and on the login page. The choice is kept in
the browser. The panel's own theme cannot be changed through an API; the --la-* design tokens and the
larsadmin-* component classes are a stable contract for extensions since 1.5. See the
theming guide.
Documentation
Full guides, in English. The Ukrainian originals are in docs/guide/; every page links to
its translation and to the same page on larsadmin.dev/en/docs, which has
the guides of the latest release with search (українською).
| Guide | Topics |
|---|---|
| 01. Installation | requirements, publish tags, config, commands |
| 02. Entities | Entity API, queries, generator |
| 03. Fields | field types, visibility, validation, list columns |
| 04. Relations | BelongsTo, HasMany, BelongsToMany |
| 05. Authorization | panel gate, policies, row scoping |
| 06. Filters | built-in and custom filters |
| 07. Actions | bulk and row actions, responses, background actions, generator |
| 08. Localization | languages, translations, switcher |
| 09. Theming | tokens, light/dark/system, CSS contract for extensions |
| 10. Extending | extension points in PHP, the component registry, built-in keys |
| 11. List UI | column picker, responsive layout |
| 12. Field blocks | panels, tabs, two columns |
| 13. Metrics | Value, Trend, Partition, Progress, scoping, caching |
| 14. Dashboards | dashboards, home page, entity cards |
| 15. Files and images | File and Image fields, disks, private files, pruning |
| 16. Global search | Ctrl/⌘+K palette, opt-in, subtitles, Laravel Scout, authorization |
| 17. Audit log | enabling, what is recorded, masking, gate, history UI, pruning, AuditRecorded |
| 18. Notifications and background actions | the bell, LarsadminNotification, queued actions, ActionResponse::file(), realtime (Reverb/Pusher), pruning |
| 19. Extensions: the public API | scaffolds, Larsadmin::extension(), defineExtension, custom fields, DataCard, tools, extension routes, overrides, fail closed, CSP, versioning, testing |
| 20. AI assistants | skills, Laravel Boost, larsadmin:ai, the MCP server and its tools, security model |
| 21. Testing | testing entities, policies, actions, filters, metrics and extensions, InteractsWithLarsadmin |
Changelog
See CHANGELOG.md.
Security
Please do not report vulnerabilities in public issues. Use GitHub private vulnerability reporting as described in SECURITY.md.
License
Larsadmin is open-source software licensed under the MIT License since version 1.0.2. Versions 1.0.0 and 1.0.1 were released under GPL-3.0-only.
Larsadmin is an independent project and is not affiliated with, sponsored or endorsed by Laravel Holdings Inc. or Laravel Nova. Laravel and Nova are trademarks of their respective owners.