Dashboard Menus
Plugins can inject menu items into the admin sidebar. The sidebar renders all registered menus automatically — no Blade code needed.
add_menu() Config Keys
| Key | Type | Required | Description |
|---|---|---|---|
id | string | Required | Unique identifier, used as parent ID for submenus. |
label | string | Required | Displayed menu text. Wrap in __() for translations. |
icon | string | Required | MDI icon class, e.g. mdi-cart. See pictogrammers.com. |
route | string | Required | Named Laravel route to link to. |
order | int | Optional | Sort order in sidebar. Lower = higher. Default 50. |
context | enum | Optional | landlord, tenant, or both. Defaults to plugin type. |
permission | string | Optional | Gate permission string required to see this menu item. |
$this->add_menu([
'id' => 'acme-loyalty-menu',
'label' => __('Loyalty Points'),
'icon' => 'mdi-star',
'route' => 'tenant.admin.acme.loyalty.index',
'order' => 80,
'context' => 'tenant',
'permission' => 'manage-loyalty',
]);
$this->add_submenu('acme-loyalty-menu', [
'id' => 'acme-loyalty-settings',
'label' => __('Settings'),
'icon' => 'mdi-cog',
'route' => 'tenant.admin.acme.loyalty.settings',
]);
Plugin Settings API
Call register_settings() to declare your plugin's options. The platform auto-generates a styled settings page with validation — no controller needed.
⚠️ Call register_settings() in routes(), not only in boot(). The plugin card's Settings button is powered by SettingsManager::hasDefinitions(), which only returns true after register_settings() has been called. Because routes() runs for every discovered plugin (active or inactive), putting the call there guarantees the button appears regardless of active state or tenant context. Call it in boot() as well (it is idempotent) so settings are available when hooks and menus run.
Available Field Types
text email url number textarea code select multiselect toggle color image repeater
Field Definition Keys
| Key | Required | Description |
|---|---|---|
key | Required | Option key used with get_option() / update_option(). |
label | Required | Human-readable field label. |
type | Required | One of the field types listed above. |
default | Optional | Default value when no setting exists. |
description | Optional | Help text shown below the field. |
required | Optional | Boolean. Adds server-side required validation. |
options | Conditional | Required for select and multiselect. Array of 'value' => 'Label'. |
$this->register_settings([
[
'key' => 'api_key',
'label' => 'API Key',
'type' => 'text',
'required' => true,
'description' => 'Your Acme API key from dashboard.acme.com',
],
[
'key' => 'mode',
'label' => 'Operating Mode',
'type' => 'select',
'default' => 'sandbox',
'options' => [
'sandbox' => 'Sandbox (testing)',
'live' => 'Live (production)',
],
],
[
'key' => 'enabled',
'label' => 'Enable Plugin',
'type' => 'toggle',
'default' => true,
],
]);
// Retrieve anywhere in your plugin:
$key = $this->get_option('api_key');
$mode = $this->get_option('mode', 'sandbox');
ℹ️ Settings are automatically scoped to the current tenant_id when the plugin type is tenant or both. Landlord-set values serve as global defaults that tenants can override.

