How a request travels through TruCMS, which services exist, and where the extension points sit. Every later chapter assumes this mental model.
Laravel 13 bootstraps from bootstrap/app.php:
routes/web.php (web group), routes/api.php, routes/console.php, health check at /up.RedirectToInstallMiddleware, which sends every request to the installer's preinstall route until dotartisan.installed is true (except install/*).web group — cookies, session, error sharing, SetLocale (localized routes), route-model binding, AuthenticateSession, then DemoClearance (a write guard for the two demo accounts when APP_DEMO is on).install/* and webhooks/*.| Alias | Class | Purpose |
|---|---|---|
auth, guest | app Authenticate, RedirectIfAuthenticated | Session authentication |
role, permission, role_or_permission | Spatie middleware | RBAC route guards |
AdminTheme | App\Http\Middleware\AdminTheme | Activates the admin theme and fires AdminBooting |
FrontTheme | App\Http\Middleware\FrontTheme | Activates the front theme, shares the dynamic CSS URL, locale and text direction |
2fa | PragmaRX\Google2FALaravel\Middleware | TOTP gate for admin routes |
RedirectIfInstalled | app middleware | Installer-only routes |
doCache / doNotCache | Spatie CacheResponse / DoNotCacheResponse | Response-cache opt in / out |
Listed in bootstrap/providers.php:
| Provider | Responsibility |
|---|---|
AppServiceProvider | Theme-option singletons, binding-source registry, HeaderComposer, the super-admin Gate::before, the page-builder-ai rate limiter, response-cache invalidation hooks, HTTPS forcing |
MetaTagsServiceProvider | SEO head tags (Meta::setMeta() macro), favicon, webmaster verification, Google Tag Manager, Open Graph and Twitter Card packages |
DotartisanServiceProvider | Registers the manager singletons (menumanager, settingsmanager, dashboard, shortcodes, app-pages, rolesmanager…), default dashboard rows, the @dashboard directive, custom validators, Module:: macros, core AppPages and core shortcodes |
WidgetsServiceProvider | Registers the built-in front widgets and, after every other provider has booted, loads persisted widget areas and instances |
LaravelModulesServiceProvider | Boots every enabled module's own service provider |
FrontendRoutingServiceProvider | Registers the catch-all {slug} route (resolve.show) after everything else so reserved prefixes never collide |
Module providers boot inside LaravelModulesServiceProvider, so anything that must see all registrations (widgets, binding-source watchers, reserved slugs) is deferred with $app->booted(...).
GET /admin/pages
-> web group
-> routes/web.php : Route::middleware([AdminTheme])->as('admin.')->prefix('admin')
-> routes/admin.php : auth, verified, 2fa -> ->can('manage page') -> PageController@index
-> view('pages.index') resolved in resources/themes/admin/pages/index.blade.phpAdminTheme::handle() does five things, in order:
setting('admin_theme'), default admin).AdminAsideMenu::register() — registers the core sidebar entries for the current user's permissions into MenuManager.RegisterSettings::register() — registers all core settings sections into SettingsManager.admin.menus* routes, BuilderMenu fills the menu-builder sources.App\Events\AdminBooting. Every module listens to it to add its own sidebar items, settings sections and menu-builder sources.The most important hook
AdminBooting fires on every admin request, after the core menu and settings are registered. A plugin that listens to it can add sidebar entries, settings sections and menu-builder sources without touching core files. Module admin routes use the same AdminTheme middleware, so your pages also receive these registrations.
GET /about
-> web group
-> catch-all route 'resolve.show' (FrontendRoutingServiceProvider)
-> FrontTheme, TwoFactorMiddleware, doCache
-> ResolvePathController@show -> SlugResolver
-> slugs row -> sluggable model -> resolvedSlugRenderer() -> renderer->render()
-> view('pages.show') (resources/themes/echo/pages/show.blade.php)Fixed routes (/, /contact, /blog, /category/{category}, /tag/{tag}, auth pages and the /user/* account area) are declared in routes/front.php, routes/app.php and routes/auth.php. Anything else at the first URL segment is treated as a slug (pages, posts, booking services, shop products). That is why modules reserve their own segments with public_paths in module.json. See Page types and public URLs.
Public routes are wrapped in Route::localized(...) where translation is supported, so locale prefixes such as /ar/about are handled by codezero/laravel-localized-routes.
A module's RouteServiceProvider wraps its route files in the same theme middleware:
Modules/Booking/app/Providers/RouteServiceProvider.php
Route::middleware(['web', 'auth', AdminTheme::class])
->as('admin.booking.')
->prefix(config('dotartisan.admin_prefix', 'admin') . '/bookings')
->group(module_path($this->name, '/routes/admin.php'));Core routes/*.php files neverrequire module routes. Disabling a module removes its routes, menu entries, widgets and scheduled tasks; its database tables stay.
TruCMS ships three themes in resources/themes/:
| Theme | Purpose |
|---|---|
admin | CoreUI-based admin panel, GrapesJS editor chrome, settings and dashboard components |
echo | The public front end: layouts, header and footer presets, the blog "publication" design, widgets, emails, auth screens, the logged-in visitor area and the page-template library |
default | Parent theme with shared static assets (images, CKEditor build). Every theme extends it |
igaster/laravel-theme adds the active theme's folder to Laravel's view paths, so view('layouts.app-layout') resolves to resources/themes/echo/layouts/app-layout.blade.php and a child theme can override any view by providing the same relative path.
Module views are namespaced (booking::admin.dashboard). The theme view finder searches for an override before the module's own folder, in resources/themes/{active theme}/vendor/{namespace}/… — for example resources/themes/echo/vendor/booking/admin/dashboard.blade.php. Views published to resources/views/modules/{alias} are mapped onto {theme}/modules/{alias}. This is how a theme restyles a module without editing it.
DotartisanServiceProvider binds string-keyed singletons that are exposed through facades. These are the extension points for modules and themes:
Facade (App\Facades\…) | Container key | Class | What you register |
|---|---|---|---|
MenuManager | menumanager | Services\MenuManager | Admin sidebar entries (MenuItems) |
MenuBuilder | menubuildermanager | Services\MenuBuilderManager | Link sources in the admin Menus builder |
SettingsManager | settingsmanager | Services\Settings\Settings | Settings sections → pages → fields |
Dashboard | dashboard | Services\Dashboard\DashboardRegistry | Dashboard rows and widgets for any named dashboard |
Widgets | SeoWidget | Services\WidgetManager | Front widget classes selectable in Appearance → Widgets |
Shortcode | shortcodes | Services\PageBuilder\ShortcodeRegistry | Page-builder shortcodes, categories and asset markers |
AppPages | app-pages | Services\AppPages\PageRegistry | Named page types ("Shop page", "Contact page") that an admin maps to a CMS page |
Roles | rolesmanager | Services\RoleManager | Team (workspace) roles and API-token abilities |
| — | BindingSourceRegistry::class | Services\PageBuilder\Bindings\BindingSourceRegistry | Data-bound section sources |
| — | PageBuilderAssetRegistry::class (scoped) | Services\PageBuilder\Assets\PageBuilderAssetRegistry | Per-request Vite, CSS and JS assets for page-builder content |
Meta | — | Butschster\Head\…\Meta | <head> tags; TruCMS adds setMeta($model) and setFont([...]) macros |
Registries are plain objects rebuilt on every request. Whatever you register must therefore run in every request that needs it. That is why menu and settings registrations happen inside an AdminBooting listener, while shortcodes, widgets and binding sources are registered in the provider's boot().
| What | Where | Notes |
|---|---|---|
| Site configuration | settings table (Akaunting) | Read with the global setting('key', $default); write with Setting::set() then Setting::save() |
| Machine/env configuration | .env | Edited by settings screens through DotenvEditor; saving runs php artisan dotartisan:optimize |
| Theme options | theme_options setting, scoped per theme | Read with theme_option('key', $default); defaults come from the theme's theme.json |
| Uploads | Spatie Media Library (media table) and the public disk | HasMedia models; settings file fields store a path in settings |
| Content | Eloquent models in app/Models and Modules/*/app/Models | BaseModel adds cache invalidation |
| Slugs | Global slugs table | Dual-written with legacy slug columns by HasSlug |
| Module state | modules_statuses.json | Enabled/disabled map used by nwidart's file activator |
doCache; never cache state-changing pages.ClearCache trait on BaseModel, plus manual Cache::remember for menus, languages, countries and widget areas.storage/app/public/css/themes/{theme}/app.css when theme options change.config, route, view); dotartisan:optimize and dotartisan:cache-views are the project's "safe" variants.QUEUE_CONNECTION defaults to sync; choose a real driver in production if you queue anything.RefreshBoundPages). Business modules schedule their own commands from configureSchedules() in their provider. routes/console.php schedules pages:refresh-bound hourly.php artisan schedule:run every minute.| Guard | Effect |
|---|---|
dotartisan.installed | False → everything redirects to the installer; several registrations skip themselves |
dotartisan.demo | Enables the demo write guard for [email protected] and [email protected]by route-name pattern (admin.*.store, admin.*.update, …) and the public /demo/* showcase |
| Super admin | The user holding the role whose id is dotartisan.super_admin_role (default 1) passes every Gate check |
| Two-factor | 2fa alias on routes/admin.php; TwoFactorMiddleware on front and user routes; both delegate to App\Services\Authenticator |
Tip for plugin authors
Because the demo guard matches route names, give admin routes conventional names (admin.myplugin.items.store, .update, .destroy) so they are protected on demo installs automatically.