Routes and views
Register routes and Blade views under your plugin's namespace without clashing with core or other plugins.
Your plugin adds routes and views like any Laravel package. Two rules:
- Namespace your views — so they don't clash with core or other plugins.
- Route names should start with a prefix — typically your plugin's slug — for the same reason.
Loading routes
In your service provider's boot():
public function boot(): void
{
$this->loadRoutesFrom(__DIR__ . '/../routes/web.php');
}
And routes/web.php:
<?php
use Acme\Awesome\Http\Controllers\AwesomeController;
use Acme\Awesome\Http\Controllers\SettingsController;
use Illuminate\Support\Facades\Route;
// Public routes (browsable by any visitor)
Route::middleware('web')->group(function () {
Route::get('/awesome', [AwesomeController::class, 'show'])->name('awesome.public');
});
// Admin routes — require cms.auth + cms.admin middleware
Route::prefix(config('cms.route_prefix'))
->middleware(['web', 'cms.auth', 'cms.admin'])
->group(function () {
Route::get('/awesome/settings', [SettingsController::class, 'index'])->name('awesome.settings');
Route::post('/awesome/settings', [SettingsController::class, 'save'])->name('awesome.settings.save');
});
Notice:
- Route names all start with
awesome.— this is whatplugin.json'smenu.routereferences, and what Contensio uses for sidebar active-state detection. - Admin routes live under
config('cms.route_prefix')(default:admin/) and usecms.auth+cms.adminmiddleware aliases that Contensio registers. - Authenticated, non-admin routes (e.g. a user-facing plugin setting) use
['web', 'auth']with no cms-specific middleware.
Middleware aliases Contensio registers
| Alias | Does |
|---|---|
cms.auth |
Require authenticated user |
cms.admin |
Require admin role |
cms.permission:X |
Require permission X (e.g. cms.permission:shop.manage) |
Loading views
public function boot(): void
{
$this->loadViewsFrom(__DIR__ . '/../resources/views', 'awesome');
}
And use them with the namespace:
return view('awesome::admin.settings', compact('settings'));
This lives in resources/views/admin/settings.blade.php inside your plugin.
Extending core layouts
Plugin admin pages typically @extends('cms::admin.layout') to get the shared admin chrome:
@extends('cms::admin.layout')
@section('title', 'Awesome Settings')
@section('breadcrumb')
<a href="{{ route('cms.admin.settings.index') }}" class="text-gray-400">Configuration</a>
<span class="mx-2 text-gray-300">/</span>
<span class="font-medium text-gray-700">Awesome</span>
@endsection
@section('content')
{{-- your content here --}}
@endsection
Language files
$this->loadTranslationsFrom(__DIR__ . '/../resources/lang', 'awesome');
Use:
{{ __('awesome::awesome.page_title') }}
The file: resources/lang/en/awesome.php:
<?php
return [
'page_title' => 'Awesome Settings',
'save' => 'Save changes',
];
Publishing assets
If your plugin ships static CSS/JS/images:
public function boot(): void
{
// ... other loaders
$this->publishes([
__DIR__ . '/../public' => public_path('vendor/awesome'),
], 'awesome-assets');
}
Users run php artisan vendor:publish --tag=awesome-assets (or Contensio runs this automatically on enable in a future version). Your Blade views reference them:
<link rel="stylesheet" href="{{ asset('vendor/awesome/style.css') }}">
Avoiding route-name clashes
Start every route name with a unique prefix. For Social Connect: socialconnect.*. For a hypothetical shop plugin: shop.*. For a community plugin: community.*.
If you forget this, Laravel throws a UrlGenerationException when two plugins register the same name.