Skip to main content
Version: v8.x

Templating & Assets

Module views are Blade templates stored under Modules/{Module}/resources/views/. Reference them from controllers using the module's alias (from module.json) as the view-namespace prefix:

return view('sample::index'); // resources/views/index.blade.php
return view('sample::admin.dashboard'); // resources/views/admin/dashboard.blade.php

A generated module ships with two views:

  • resources/views/layouts/master.blade.php — a basic HTML skeleton with @yield('content').
  • resources/views/index.blade.php — extends the master layout.

Generating views

php artisan module:make-view posts.index Sample
# → Modules/Sample/resources/views/posts/index.blade.php

Using views

A typical module view extends a layout and yields content:

{{-- Modules/Sample/resources/views/posts/index.blade.php --}}
@extends('sample::layouts.master')

@section('content')
<h1>Posts</h1>

@foreach ($posts as $post)
<article>
<h2>{{ $post->title }}</h2>
<p>{{ $post->excerpt }}</p>
</article>
@endforeach
@endsection

You can also extend phpVMS's main layouts directly:

@extends('layouts.app')

Or include partials from other modules:

@include('shop::partials.cart-summary')
@extends('admin::layouts.dashboard')

View composers

If a view always needs the same data, register a view composer in the module's service provider — usually inside boot():

// Modules/Sample/app/Providers/SampleServiceProvider.php

public function boot(): void
{
parent::boot();

$this->app['view']->composer('sample::*', function ($view) {
$view->with('siteSettings', \App\Models\Setting::all());
});
}

Now every view in the sample namespace receives $siteSettings automatically.

See the Laravel view composers docs for the full pattern.

Blade components

Blade components are reusable view fragments. Generate one with:

php artisan module:make-component Alert Sample

This creates two files:

  • Modules/Sample/app/View/Components/Alert.php — the PHP class.
  • Modules/Sample/resources/views/components/alert.blade.php — the markup.
namespace Modules\Sample\View\Components;

use Illuminate\View\Component;

class Alert extends Component
{
public function __construct(
public string $type = 'info',
public ?string $message = null,
) {}

public function render()
{
return view('sample::components.alert');
}
}
{{-- resources/views/components/alert.blade.php --}}
<div class="alert alert-{{ $type }}">
{{ $message ?? $slot }}
</div>

Use it from any view, prefixed with the module alias:

<x-sample::alert type="success" :message="$message" />

<x-sample::alert type="warning">
Heads up — your flight plan was modified.
</x-sample::alert>

Anonymous (view-only) components

If you don't need PHP logic, generate just the view:

php artisan module:make-component-view card Sample
# → Modules/Sample/resources/views/components/card.blade.php
<x-sample::card>
<h3>{{ $title }}</h3>
{{ $slot }}
</x-sample::card>

Overriding views

If an operator wants to customise your module's views without editing the module itself, they have two options. Both work because the module's service provider registers the app override path first when calling loadViewsFrom, so Laravel resolves the operator's copy before the module's own.

1. Publish the views

php artisan vendor:publish

…and pick your module from the list. This copies the Blade files into resources/views/modules/{alias}/ in the host app, where edits survive module updates.

2. Manually copy the file

Copy the specific view from Modules/{Module}/resources/views/... into resources/views/modules/{alias}/... in the host app. The override is picked up immediately — no vendor:publish required.

note

The override path uses the module's lowercase alias (sample), not its StudlyCase name. For a module aliased sample, overrides go to resources/views/modules/sample/.

Compiling assets (Vite)

Module assets live under Modules/{Module}/resources/assets/:

Modules/Sample/
├── package.json
├── vite.config.js
└── resources/
└── assets/
├── js/app.js
└── sass/app.scss

There are two ways to compile module assets — pick one per project.

A single vite.config.js at the project root collects assets from every enabled module. The host project's main vite.config.js should look like:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import collectModuleAssetsPaths from './vite-module-loader.js';

async function getConfig() {
const paths = [
'resources/css/app.css',
'resources/js/app.js',
];

return defineConfig({
plugins: [
laravel({
input: await collectModuleAssetsPaths(paths, 'Modules'),
refresh: true,
}),
],
});
}

export default getConfig();

Each module's vite.config.js exports its asset paths:

// Modules/Sample/vite.config.js
export const paths = [
'Modules/Sample/resources/assets/sass/app.scss',
'Modules/Sample/resources/assets/js/app.js',
];

Render module assets in your layout:

@vite(\Nwidart\Modules\Module::getAssets())

Build everything from the project root:

npm run build # production
npm run dev # dev server with HMR

Option B — Per-module Vite

If you'd rather keep each module fully self-contained, publish the per-module loader:

php artisan vendor:publish \
--provider="Nwidart\Modules\LaravelModulesServiceProvider" \
--tag="vite"

Configure the module's vite.config.js:

import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
build: {
outDir: '../../public/build-sample',
emptyOutDir: true,
},
plugins: [
laravel({
publicDirectory: '../../public',
buildDirectory: 'build-sample',
input: [
__dirname + '/resources/assets/sass/app.scss',
__dirname + '/resources/assets/js/app.js',
],
refresh: true,
}),
],
});

Render the assets in a layout using the module_vite() helper:

{{ module_vite('build-sample', 'resources/assets/sass/app.scss') }}
{{ module_vite('build-sample', 'resources/assets/js/app.js') }}

Build from inside the module:

cd Modules/Sample
npm install
npm run build

Further reading