Frontend Pages
v1.3
Modules can add public-facing pages (visible to guests and logged-in users) that integrate with the active store theme.
Two tracks
| Track | When to use | How |
|---|---|---|
| 1 — Extend active theme | Pages that should blend into the store (pricing page, landing pages) | @extends('theme::layouts.app') |
| 2 — Custom layout | Fully branded UI separate from the store | Ship your own layout blade |
You can use both tracks in the same module for different pages.
Track 1 — Extend the active theme
@extends('theme::layouts.app', ['title' => 'My Page'])
@section('content')
<div class="max-w-5xl mx-auto px-4 py-16">
<h1 class="text-4xl font-bold">Hello</h1>
</div>
@endsectiontheme:: resolves to whichever theme is currently active (currently tailwind). Your page inherits the header, footer, navigation, and dark mode support automatically.
Available stacks
Push content into the layout's stacks from any view:
@push('ch_head')
<link rel="stylesheet" href="{{ asset('modules/my-plugin/css/styles.css') }}">
@endpush
@push('ch_footer')
<script src="{{ asset('modules/my-plugin/js/app.js') }}"></script>
@endpush| Stack | Position |
|---|---|
ch_head | Inside <head>, after core styles |
ch_footer | Before </body>, after core scripts |
Track 2 — Custom layout
Ship a layout blade inside your module and extend it from your page views:
modules/MyPlugin/
└── resources/
└── views/
├── layouts/
│ └── app.blade.php ← your custom layout
└── pages/
└── dashboard.blade.php{{-- layouts/app.blade.php --}}
<!DOCTYPE html>
<html>
<head>
<title>@yield('title', 'My App')</title>
<link rel="stylesheet" href="{{ asset('modules/my-plugin/css/app.css') }}">
</head>
<body class="bg-gray-950 text-white">
@yield('content')
<script src="{{ asset('modules/my-plugin/js/app.js') }}"></script>
</body>
</html>{{-- pages/dashboard.blade.php --}}
@extends('my-plugin::layouts.app', ['title' => 'Dashboard'])
@section('content')
<div>Your fully custom UI here.</div>
@endsectionRegistering public routes
Add public routes in routes/web.php. No authentication middleware is required unless you want to restrict access:
Route::prefix('my-plugin')
->middleware(['web'])
->name('my-plugin.')
->group(function () {
Route::get('/pricing', [PricingController::class, 'index'])->name('pricing');
Route::get('/features', [FeaturesController::class, 'index'])->name('features');
});Assets
Compile module CSS/JS to public/modules/my-plugin/ so they're web-accessible. Reference them with asset():
asset('modules/my-plugin/css/app.css')
asset('modules/my-plugin/js/app.js')If you use a build tool (Vite/Mix), configure its output to target public/modules/my-plugin/. The module folder itself only needs to contain source files — compiled output goes to public/.
Flash messages
The tailwind theme's layout renders a flash partial automatically. Use standard Laravel session flashing from your controllers:
return redirect()->route('my-plugin.pricing')
->with('success', 'Done!')
->with('error', 'Something went wrong.');