Sidebar navigation
Place your plugin's admin links in the sidebar — Root, Tools, or Appearance, with one or many entries.
Contensio's admin sidebar has three extension placements. Your plugin declares where its links belong in plugin.json's menu block — no code required.
The three placements
| Placement | Where it appears | Good for |
|---|---|---|
root |
Top-level, alongside Dashboard / Pages / Posts | Main user-facing plugin features |
tools |
Inside the collapsible Tools dropdown | Utilities (exports, imports, audits) |
appearance |
Inside the collapsible Appearance dropdown | Visual / theme-related settings |
none |
No sidebar entry at all | Plugin is reached via other means |
Single entry
"menu": {
"placement": "tools",
"label": "Social Connect",
"icon": "bi-link-45deg",
"route": "socialconnect.settings",
"permission": "plugins.configure"
}
- icon — a Bootstrap Icons class (
bi-*). Contensio self-hosts the full icon set. - route — a Laravel route name. If the route doesn't exist, the entry is hidden (safe fallback during development).
- permission — optional. If set, users without this permission don't see the entry.
Multiple entries
A large plugin often needs more than one sidebar link. menu accepts an array of entries:
"menu": [
{ "placement": "root", "label": "Shop", "icon": "bi-bag", "route": "shop.index" },
{ "placement": "root", "label": "Orders", "icon": "bi-receipt", "route": "shop.orders" },
{ "placement": "tools", "label": "Export orders", "icon": "bi-box-arrow-down", "route": "shop.export" },
{ "placement": "appearance", "label": "Storefront", "icon": "bi-brush", "route": "shop.storefront" }
]
A community plugin might register:
"menu": [
{ "placement": "root", "label": "Community", "icon": "bi-people", "route": "community.index" },
{ "placement": "tools", "label": "Moderation","icon": "bi-flag", "route": "community.moderation" }
]
Each entry is independent — each respects its own permission rule, its own route check, its own placement.
Active-state detection
Contensio auto-detects when the current page belongs to your plugin by matching the route name. Sidebar dropdowns (Tools, Appearance) auto-open when one of their children is active.
Route matching uses prefix — if your plugin registers shop.settings and the current route is shop.settings.save, the shop.settings sidebar entry lights up. This works well as long as you name related routes with a common prefix.
Permission gating
Set permission to a string that matches a permission you've declared in your plugin (see Permissions) or a core permission. Users without the permission don't see the entry at all — not a greyed-out link, just hidden.
Best-practice core permissions to check against:
plugins.configure— for configuration screens meant for admins only- Role-specific ones via
$user->hasRole('editor')in a custom gate (rare)
What if I don't want a sidebar entry?
Set "placement": "none" (or omit menu entirely). Your plugin's pages are still reachable via their routes — they just don't show up in the sidebar. Useful if the entry point is elsewhere (e.g. a settings-hub tile via Hook::add('settings.hub_cards', ...)).
Testing
After changing plugin.json:
- Save the file
- Refresh the admin
- The sidebar updates live — no cache-clear needed
If the entry doesn't appear:
- Check the permission — does your user actually have it?
- Check the route name exists (
php artisan route:list | grep shop.index) - Check for JSON syntax errors in
plugin.json
See also
- plugin.json reference — full manifest spec
- Permissions — declaring your own permissions