Skip to content
Bondry

Routes, permissions and admin screens

Wiring a module into the front end, the group matrix and the admin navigation.

Routes#

Two files, loaded from boot():

PHP
$this->loadRoutesFrom(__DIR__.'/../routes/web.php');
$this->loadRoutesFrom(__DIR__.'/../routes/admin.php');

Name every route with your slug as the prefix (directory.index, admin.directory.settings). That is what makes Route::has() a reliable guard for other modules, and what lets the panel link to your settings screen.

Admin routes go behind the panel middleware, exactly like the core ones. Never roll your own admin authentication.

Permissions#

Declare them in the manifest:

JSON
"provides": { "permissions": ["directory.view", "directory.manage"] }

They are registered into the group matrix on install and appear in Members > Member groups alongside every other module's permissions. Check them the usual way:

PHP
Gate::allows('directory.manage');

Three rules:

  • Never invent a parallel permission system. The group matrix is the one answer to "who can do this", and buyers already understand it.
  • Check on the server, always. Hiding a button is presentation, not security.
  • Scope where it makes sense. A permission per area of your module beats a single on-off switch.

Admin navigation#

The panel navigation is built from a registry, not hardcoded. You contribute through the admin.menu slot in boot():

PHP
Hooks::on('admin.menu', fn (HookContext $ctx) => [
    'label' => __('directory::admin.title'),
    'url'   => route('admin.directory.settings'),
    'order' => 40,
]);

Only add entries the current administrator can actually reach. A menu item that leads to a 403 is noise.

UI slots#

The core declares these, and a module can declare its own:

admin.menu, admin.dashboard.widgets, profile.tabs, profile.sidebar, settings.pages, search.sources, editor.toolbar, member.actions, head.meta, body.end.

Registering:

PHP
Hooks::on('profile.tabs', fn (HookContext $ctx) => [
    'label' => __('directory::labels.items'),
    'url'   => route('directory.member', $ctx->get('member')),
    'order' => 20,
]);

Consuming, in a Blade view:

Blade
@hook('profile.tabs', ['member' => $member])

Contributions are collected, ordered by order and rendered.

Search#

Contribute to the site search through search.sources. Your source is responsible for applying your own visibility rules: a result must never appear for someone who could not open the page it points at.

Screens#

Admin screens use the panel components so your module looks like the product rather than like a plugin. In particular:

  • square icon buttons of a uniform size, with a chevron dropdown for the secondary actions, in every list;
  • the product modal for confirmation, never window.confirm;
  • selects with the custom chevron, never the bare native one;
  • zero axe violations on every screen you add, in both light and dark.