Contensio logo

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:

  1. Namespace your views — so they don't clash with core or other plugins.
  2. 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 what plugin.json's menu.route references, and what Contensio uses for sidebar active-state detection.
  • Admin routes live under config('cms.route_prefix') (default: admin/) and use cms.auth + cms.admin middleware 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.