laravel-vue-cms maintained by j87nl
j87nl/laravel-vue-cms
Inertia and Vue admin for j87nl/laravel-cms: pages, blocks, media, menus, redirects and site settings, built on TranquilTools CrudBuilder, FormBuilder and TableBuilder.
The Vue pages stay inside the package. The app's Inertia resolver loads them from the package, and an app overrides a single page through the configurable page names in config/vue-cms.php.
Requirements
- PHP 8.5, Laravel 13 and Inertia 3 with Vue 3
j87nl/laravel-cmstranquil-tools/laravel-vue-form-builderandtranquil-tools/laravel-vue-table-builder(installed as dependencies)laravel-vue-i18nand a$can(permission)helper on the Vue app
Installation
composer require j87nl/laravel-vue-cms
php artisan vendor:publish --tag=vue-cms-config
The admin routes live under vue-cms.admin.prefix (default admin/cms) behind vue-cms.admin.middleware (default web and auth), and every screen checks one of the CMS permissions.
Vite and the page resolver
Add the aliases and load the package pages next to your own:
// vite.config.ts
resolve: {
alias: {
'@cms': fileURLToPath(new URL('./vendor/j87nl/laravel-vue-cms/resources/js', import.meta.url)),
'@form-builder': fileURLToPath(new URL('./vendor/tranquil-tools/laravel-vue-form-builder/resources/js', import.meta.url)),
'@table-builder': fileURLToPath(new URL('./vendor/tranquil-tools/laravel-vue-table-builder/resources/js', import.meta.url)),
},
},
// resources/js/app.ts
const appPages = import.meta.glob<DefineComponent>('./pages/**/*.vue');
const cmsPages = import.meta.glob<DefineComponent>('@cms/pages/**/*.vue');
resolve: async (name) => {
const page = name.startsWith('cms/')
? Object.entries(cmsPages).find(([path]) => path.endsWith(`/pages/${name.slice(4)}.vue`))?.[1]
: appPages[`./pages/${name}.vue`];
return (await page!()).default;
},
Add "@cms/*": ["./vendor/j87nl/laravel-vue-cms/resources/js/*"] to the paths in tsconfig.json, and add the package's .vue files to Tailwind's sources so their classes are generated:
@source '../../vendor/j87nl/laravel-vue-cms/resources/js/**/*.vue';
Host app contract
- Confirm dialog: the package never uses
window.confirm. Provide a promise-based confirm function from your layout:provide('confirm', (message: string) => Promise<boolean>). Without it every confirmation resolves tofalse, so destructive actions do nothing. - Breadcrumbs: pages set a
breadcrumbslayout prop throughsetLayoutProps. Your admin layout reads it and renders the trail. - Translations: strings use
vue-cms::keys throughlaravel-vue-i18n. Make the package'slangdirectory available to the i18n Vite plugin, for example with alang/vendor/vue-cmssymlink tovendor/j87nl/laravel-vue-cms/lang.
Navigation and permissions
The package pushes its menu items into the config key named by vue-cms.navigation_config_key (default j87nl.navigation). Each item has a key, a translation label, a route, a Lucide icon name (FileText, Image, Menu, Signpost, Settings2), a permission and an order. The app builds its sidebar from that key.
Map the CMS permissions to your roles: cms.pages.edit, cms.pages.publish, cms.menus, cms.media, cms.redirects and cms.settings. Publishing, taking offline, planning and changing a published page all need cms.pages.publish.
Toasts
Success messages are flashed with Inertia::flash() under vue-cms.toast_flash_key (default toast) as { type, message }.
Testing
composer update
vendor/bin/pest
License
MIT