Routes, Controllers and Views

Give your plugin admin screens and public pages that look and behave like the rest of TruCMS: the right middleware, the right layout component, breadcrumbs, SEO and theme-overridable views.

Route files and the route provider

A plugin never touches routes/*.php of the core. It has its own RouteServiceProvider (listed in the main provider's $providers) that wraps each route file in a middleware stack. Keep admin and public routes in separate files so each gets the right theme:

Modules/Booking/app/Providers/RouteServiceProvider.php (trimmed)

public function map(): void
{
    Route::middleware(['web', 'auth', AdminTheme::class])           // admin
        ->as('admin.booking.')
        ->prefix(config('dotartisan.admin_prefix', 'admin') . '/bookings')
        ->group(module_path($this->name, '/routes/admin.php'));

    Route::middleware(['web', FrontTheme::class])                   // public
        ->as('booking.')
        ->prefix(config('booking.front_prefix', 'booking'))
        ->group(module_path($this->name, '/routes/front.php'));
}
AudienceMiddlewareName prefix / URL prefix
Admin (same as core)web, auth, verified, 2fa, AdminTheme (+ permission:access {alias} for a whole area)admin.{alias}. / {admin_prefix}/{alias}
Public pagesweb, FrontTheme (+ doCache for cacheable pages){alias}. / {alias} (also declare it in public_paths)
Signed-in visitorsweb, FrontTheme, TwoFactorMiddleware, auth, verified{alias}.account. / user/… or {alias}/account
Staff outside the adminas above + permission:access {alias} staff cabinetBooking uses staff/bookings
Webhooksweb (no theme) with ->withoutMiddleware(PreventRequestForgery::class){alias}.webhook — the global CSRF exception only covers install/* and webhooks/*
JSON APIapi, auth:sanctumapi.{alias}. / api/{alias} (TruCommerce) or v1/…

Locale prefixes

Core public routes are wrapped in Route::localized(function () { ... }) (codezero/laravel-localized-routes), producing /ar/about. Bundled business modules do not localize their routes (URLs like /booking and /shop carry no prefix); the active locale comes from the session/cookie via SetLocale and FrontTheme. Decide per plugin; if you localize, build links with route() only.

Controllers

Layouts

ComponentRendersUse for
<x-admin-layout>layouts.app (admin theme)Every admin screen
<x-admin-guest>layouts.guest (admin)Admin login/reset
<x-front-layout>layouts.app-layout (Echo)Public pages with header, breadcrumbs, footer
<x-front-fullwidth-layout>layouts.fullwidth-layoutEdge-to-edge public pages
<x-front-guest>layouts.guest-layoutLogin/registration style pages

The public layout

Modules/Playground/resources/views/front/index.blade.php

<x-front-layout>
    <div class="container py-5"><h1>Hello from a plugin</h1></div>
</x-front-layout>

Props and slots of <x-front-layout>:

Wrap the layout in your own component when your plugin always needs the same assets (Booking's <x-booking::front.layout> pushes its Vite assets once and offers a sidebarGroup prop).

SEO and breadcrumbs on public pages

use App\Facades\Meta;

Meta::setTitle('Playground page');
Meta::setDescription('A page served by a plugin.');
Meta::setMeta($model);        // macro: title, description, canonical, Open Graph, Twitter card from a model
                              // (needs title/meta_title, meta_description, optional 'og-image' media)

routes/breadcrumbs.php

Breadcrumbs::for('playground.index', function (BreadcrumbTrail $trail): void {
    $trail->parent('front');                       // "Home"
    $trail->push('Playground', route('playground.index'));
});

The public layout renders breadcrumbs for the current route name, so a named public route without a definition simply shows none (the front breadcrumbs are lenient); the admin ones are not.

Let an administrator choose a CMS page instead

Booking and TruCommerce register page types ("Shop page", "Booking services") that let an administrator assign a designed CMS page to a module route. The module controller then redirects to that page when one is assigned:

php (pattern from BookingPageRedirector)

$target = AppPages::selectedPageUrl('booking.services');   // URL of the chosen CMS page, or null
if ($target !== null && rtrim($target, '/') !== rtrim(url()->current(), '/')) {
    return redirect()->to($target, 301);
}

Views: namespaces and overrides

Assets

See Package, install and update (distributable plugins) and Build and assets (in-repo modules). In short: pre-built files published to public/modules/{alias} and referenced with asset(), or Vite entries added to the root vite.config.js and loaded with @vite() inside the page_header/page_scripts stacks.

AJAX, JSON and webhooks