Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Page Manager Laravel Package

remipou/nova-page-manager

Nova Page Manager is a Laravel Nova package for managing CMS-style pages from your admin panel. It provides page resources with slugs, publishing controls, and customizable content/templates, making it easy to maintain static or editable site pages without a full CMS.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and publish

composer require remipou/nova-page-manager
php artisan vendor:publish --provider="Remipou\NovaPageManager\NovaPageManagerServiceProvider"
php artisan migrate

Publish tags vary by release — read vendor/remipou/nova-page-manager/README.md before running vendor:publish, and diff the config file against the package default so you know what is actually configurable (route prefix, middleware, model class, view namespace).

2. Register it in Nova

The package exposes either a Nova Tool or a Resource depending on version. Check NovaServiceProvider:

// If a Tool
public function tools()
{
    return [ new \Remipou\NovaPageManager\NovaPageManager ];
}

// If a Resource
public function resources()
{
    return [ new \Remipou\NovaPageManager\Page ];
}

Run php artisan nova:publish if the package ships compiled JS assets — otherwise the menu entry renders blank.

3. Read the migration first

Before writing any content, open the package migration and note the actual columns (slug, title, content, template, published, sort, meta fields). Everything you build — routes, navigation, sitemaps — keys off those columns, and guessing them is the fastest way to burn an afternoon.

4. First use case

Create an "About" page in Nova with slug about, publish it, and wire a catch-all frontend route:

Route::get('/{slug}', [PageController::class, 'show'])
    ->where('slug', '.*')
    ->name('page.show');
public function show(string $slug)
{
    $page = Page::where('slug', $slug)->published()->firstOrFail();

    return view($page->template ?? 'pages.default', compact('page'));
}

That single pattern — Nova for editing, one controller for rendering — covers 80% of day-to-day use of this package.


Implementation Patterns

Keep developers out of the content loop. The value of the package is that a marketer edits /terms in Nova while you never touch a Blade file. Push layout, header/footer, and SEO components into your app layout and let the page resource own only the body copy.

Template switching per page. Add a template select field in the Nova resource if the package doesn't ship one, then resolve the view dynamically:

$view = match ($page->template) {
    'landing' => 'pages.landing',
    'legal'   => 'pages.legal',
    default   => 'pages.default',
};

return view($view, compact('page'));

This is the cleanest way to get multiple page shapes out of a single resource without one giant @if in Blade.

Cache rendered pages, invalidate on save. Static pages are read constantly and written rarely — the ideal cache target:

// Page model
protected static function booted()
{
    static::saved(fn ($p) => Cache::forget("page:{$p->slug}"));
    static::deleted(fn ($p) => Cache::forget("page:{$p->slug}"));
}
public function show(string $slug)
{
    $html = Cache::rememberForever("page:{$slug}", function () use ($slug) {
        $page = Page::where('slug', $slug)->published()->firstOrFail();
        return view($page->template ?? 'pages.default', compact('page'))->render();
    });

    return response($html);
}

Build navigation from the same table. Sorted, published pages make a free menu source:

View::composer('layouts.app', function ($view) {
    $view->with('nav', Page::published()
        ->orderBy('sort')
        ->get(['title', 'slug']));
});

Cache this too — it runs on every request.

Seed pages for repeatable environments. Legal pages, cookie policies, and landing copy should exist on every fresh install and in CI:

Page::updateOrCreate(
    ['slug' => 'privacy'],
    ['title' => 'Privacy Policy', 'content' => view('legal.privacy')->render(), 'published' => true]
);

updateOrCreate on slug (not id) makes the seeder idempotent across environments.

Draft previews. Wrap an authenticated preview route around the same controller, bypassing the published scope:

Route::middleware('auth')->get('/preview/{slug}', [PageController::class, 'preview']);

Sitemap integration. Because slugs live in the DB, the sitemap must be generated, not hand-written:

Route::get('/sitemap.xml', fn () => response()
    ->view('sitemap', ['pages' => Page::published()->get()])
    ->header('Content-Type', 'text/xml'));

Multi-locale (if supported). If the schema has a locale column, treat (slug, locale) as the natural key and scope the controller by app()->getLocale(). If it doesn't, use slug prefixing (/en/about) rather than fighting the package.


Gotchas and Tips

Age is the headline risk. Last release August 2022 means it predates Nova 4/5 and Laravel 10/11 conventions. Check composer.json constraints in vendor/ before assuming compatibility; expect to either pin Nova/Laravel, fork the service provider, or add a replace/conflict entry. Budget an hour for this in a greenfield upgrade.

Do not trust the register method. Whether it's a Tool or a Resource changed across versions and is the single most common "installed but nothing appears in the sidebar" cause. Verify in the vendor source, not the README.

Route ordering will bite you. A {slug} catch-all must be registered after every other route, and it must not swallow login, admin, or Nova's own /nova path. Constrain it:

->where('slug', '^(?!nova|admin|api|login).*$')

Or scope it under a prefix (/p/{slug}) and save yourself the debugging.

Slug uniqueness with soft deletes. If the model soft-deletes, a plain unique(slug) index prevents reusing a slug after deletion. Use a composite unique with deleted_at or hard-delete; otherwise editors get an opaque SQL error when re-creating an old page.

Sanitize stored HTML. A WYSIWYG field renders straight into Blade. Either use {!! $page->content !!} with a deliberate, documented trust boundary, or pipe through a sanitizer. Never render editor-supplied HTML from a lower-trust role.

Publishing state leaks if you forget the scope. If published is a boolean rather than a published_at timestamp, remember drafts are only hidden where you explicitly scope — the Nova index and the frontend query are independent. Write a test asserting a draft returns 404.

Overrides get clobbered by republishing. Custom views belong in resources/views/vendor/nova-page-manager/, not in the package path. Same for the config — never edit vendor/. Running vendor:publish --force during an upgrade will overwrite anything you put in the wrong place.

No factories. Package models rarely ship test factories. Write your own immediately; without one, your feature tests will end up bypassing the package and stop testing the thing you integrated.

Debugging checklist for a missing page:

  1. php artisan route:list | grep page — is your controller even registered?
  2. Is the slug exactly what you typed? Slugs are often auto-generated with Str::slug() on save — inspect the DB.
  3. published true, and deleted_at null?
  4. config('nova-page-manager.route_prefix') — did a published config override the default?
  5. php artisan route:clear && php artisan config:clear after touching the config.

Extension points to prefer. Extend the package's Page model in your app (app/Models/Page.php) and repoint the config's model key, rather than patching the vendor class. Same for the controller — bind your own controller in the config or route and leave the package's file untouched, so composer update never conflicts with your work.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource