marque / skipper
Admin panel for Marque - renders the admin screens packages register
Requires
- php: ^8.3
- illuminate/support: ^13.0
- illuminate/view: ^13.0
- livewire/livewire: ^4.0
- marque/deck: ^2.0
- marque/trove: ^4.2|^5.0
Requires (Dev)
- laravel/pao: ^1.1
- mockery/mockery: ^1.6
- orchestra/testbench: ^11.0
- pestphp/pest: ^4.7
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-07 06:57:00 UTC
README
The admin panel for Marque. Renders the admin screens other packages register — and knows nothing about any of them.
Install
composer require marque/skipper
The panel appears at /admin, named admin.index.
The idea
Skipper does not own any admin screens. Packages declare theirs against
marque/trove's AdminScreenRegistry, and skipper renders whatever is there.
That direction matters: nothing depends on skipper. A package registering a screen depends only on trove, which every Marque deployment already installs, so it behaves identically whether or not the panel is present. Install skipper and the screens appear; leave it out and nothing breaks. That is what makes third-party admin screens possible at all.
Registering a screen
From your own service provider's boot():
use Marque\Trove\Enums\Role; use Marque\Trove\Registry\AdminScreen; use Marque\Trove\Registry\AdminScreenRegistry; $this->app->make(AdminScreenRegistry::class)->register(new AdminScreen( identifier: 'client-whitelist', label: 'Client Whitelist', component: 'my-package-client-whitelist', // a Livewire component path: 'admin/clients', minimumRole: Role::Moderator, icon: 'pencil', group: 'Tracker', position: 20, ));
| Field | Purpose |
|---|---|
identifier |
Unique across all packages. The route name is derived as admin.<identifier>, unless your package binds its own route at that path (see below) |
label |
What the panel shows |
component |
The Livewire component that renders the screen |
path |
Where it lives, relative to the panel: the first segment stands for the panel prefix and is replaced by skipper.prefix (admin/clients is served at /staff/clients with the prefix staff); a single-segment path is used as-is under the prefix |
minimumRole |
The floor. Enforced on the listing and on the request |
icon |
Optional, rendered from deck's icon set, which is small: see deck's Icons for the names. Any other name renders nothing |
group |
Optional heading. Ungrouped screens fall under skipper.default_group |
position |
Ordering within the group. Lower first |
Registering a duplicate identifier throws rather than silently replacing the existing
screen — an accidental collision should fail at boot, not render the wrong thing months
later.
Registration is legal any time up to booted, so it does not matter whether your provider
boots before or after skipper's.
A package can serve the screen from its own route. If a route that answers GET is
already bound at the screen's declared path, skipper doesn't generate one, under any prefix: the panel links to
the package's route, and that route keeps its own name and answers its own path, with its
own middleware and authorisation. usarrs does this, serving admin/users as
admin.users.index. Skipper never serves such a screen itself, by its identifier or any
other URL; those answer 404. Skipper registers its screen routes once every provider has
booted, last, and keeps its admin/{screen} catch-all off any path already bound under the
prefix. So a route bound in a provider's register() or boot(), or in a route file, wins
whatever order the providers load in, cached or not. A route a provider adds from its own
booted() callback comes too late for that, so under the default prefix a late route at a
path that isn't a screen can lose to the catch-all; a screen with a late route is still
linked to it and never served by skipper. The package's route is the only way to its
screen, so the package must protect it itself; skipper's minimumRole check (below)
covers only the screens skipper serves.
Stability
The registration contract is public API from trove 4.2. Marque\Trove\Registry\
(AdminScreen, AdminScreenRegistry, and their public surface) follows semver on
marque/trove. Build against it.
What is not covered by that promise: how skipper renders the panel. The views, the grouping presentation and the layout are skipper's own surface and version with skipper. Your screen is a Livewire component you own; the panel just lists and routes it.
Permissions
minimumRole is checked twice, against the same registry entry: once when building the
panel listing, and once when the request is authorised (on the routes skipper generates;
see above for a package's own route). Filtering a menu is not protecting
a screen — without the second check, a moderator reaches an admin screen by typing its URL.
Roles come from trove (user → uploader → moderator → admin → owner, the last
from trove 5.0), and a screen is visible
to any role at or above its floor.
The panel itself (/admin) answers 403 to someone no registered screen admits, the same as
each screen does, rather than showing them an empty page at an admin URL. With nothing
registered at all, it renders and says so: that's an install to finish, not a refusal. If
the screens open to someone are registered but none has a route bound, the panel says that
instead.
Config
php artisan vendor:publish --tag=skipper-config
| Key | Default | Notes |
|---|---|---|
middleware |
['web', 'auth', 'verified'] |
Who gets through the door. Authorisation is separate |
prefix |
admin |
Where the panel and the screens it routes live. A screen's path loses its first segment (unless it has only one) and takes this prefix |
title |
Administration |
Panel heading |
layout |
deck::layouts.app |
Point at your own to reshape the chrome |
default_group |
General |
Heading for screens that declare no group |
Views
php artisan vendor:publish --tag=skipper-views
Published views land in resources/views/vendor/skipper and override the packaged ones.
Requirements
PHP 8.3+, Laravel 13+, marque/trove, marque/deck, Livewire 4.
License
MIT. See LICENSE.