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

Page Laravel Package

litecms/page

LiteCMS Page provides page management for Laravel applications, letting you create, edit, and publish static content pages with a ready-made admin interface, routing, and templating.

View on GitHub
Deep Wiki
Context7

Getting Started

litecms/page ships no tutorial-grade docs, so the first hour is source recon, not setup. Install into a sandbox branch you are willing to throw away:

composer require litecms/page

Then read vendor/litecms/page/ in this order — it answers 90% of your questions faster than experimentation:

  1. src/Providers/*ServiceProvider.php — what gets registered: routes, views, migrations, Blade directives, admin menu entries.
  2. src/Models/Page.php — $fillable, $casts, slug/route helpers, relationships.
  3. database/migrations/* — the real schema. Do not trust the README's field list.
  4. routes/*.php — route names and URI prefixes for the admin CRUD and the public show route.
  5. resources/views/ — the Blade templates you will end up overriding.

Publish and migrate:

php artisan vendor:publish --provider="Litecms\Page\Providers\PageServiceProvider"
php artisan migrate
php artisan route:list | grep -i page

If the provider registers publish tags, publish in separate tagged calls (config, views, migrations, assets) so you can re-publish one without clobbering your edited views. Check php artisan vendor:publish --list for available tags.

First use case — a static page resolved by slug. The canonical flow is: an editor creates a page in the admin, the package persists it, and a public route resolves /{slug} to a Blade view:

// app/Http/Controllers/PageController.php
public function show(string $slug)
{
    $page = Page::published()->where('slug', $slug)->firstOrFail();

    return view($page->template ?? 'pages.default', compact('page'));
}
{{-- resources/views/pages/default.blade.php --}}
@extends($page->layout ?? 'layouts.app')

@section('meta')
    <title>{{ $page->title }}</title>
    <meta name="description" content="{{ $page->meta_description }}">
@endsection

@section('content')
    {!! $page->body !!}
@endsection

Before wiring your own route, confirm the package hasn't already registered a catch-all — check route:list first to avoid a duplicate/conflicting registration.

Implementation Patterns

Wrap the model behind a contract. Given zero dependents and a single maintainer, do not let Litecms\Page\Models\Page leak into controllers and views. Bind your own interface and return a DTO:

// app/Contracts/PageRepository.php
interface PageRepository
{
    public function findBySlug(string $slug): ?PageData;
    public function published(): Collection;
}

This is the seam that lets you swap in a native module later without touching the front end.

Cache aggressively, invalidate on write. Page content is read-heavy and cache-friendly, but do not assume the package does this for you. Put it in your own layer using model events:

Page::saved(fn ($page) => Cache::forget("page:{$page->slug}"));
Page::deleted(fn ($page) => Cache::forget("page:{$page->slug}"));
public function findBySlug(string $slug): ?PageData
{
    return Cache::rememberForever("page:$slug", fn () =>
        PageData::fromModel(Page::published()->where('slug', $slug)->first())
    );
}

Draft preview without exposing drafts. Never let the public route serve unpublished rows. Preview via a signed, time-limited URL that sits behind auth:

Route::get('/preview/{page}', fn (Page $page) =>
    view($page->template, ['page' => $page])
)->middleware(['web', 'auth', 'signed'])->name('page.preview');

Slug renames need redirects. Editors will change slugs on live pages. On save, if the slug changed, write a 301 record into your own redirects table — the package almost certainly does not track history.

Navigation and menus. Pull published pages once per request (cached) and build nav from them rather than hardcoding links:

$nav = app(PageRepository::class)->published()->where('in_menu', true);

Write your own feature tests on the critical paths — publish state, slug uniqueness, public 404 on drafts, and admin authorization. There is no visible test suite to inherit:

it('hides draft pages from the public route', function () {
    $page = Page::factory()->create(['published' => false]);
    $this->get("/{$page->slug}")->assertNotFound();
});

Admin customisation. Override the admin views by publishing to resources/views/vendor/page/ and adding fields (SEO, OG image, canonical) via your own migration, not by editing vendor files — the fork should be a composer path repository you control, not edits in vendor/.

Gotchas and Tips

  • Catch-all route collisions. A /{slug} route registered by the package will shadow any later /{a}/{b} style app routes, producing confusing 404s or accidental content exposure. Always check route:list and constrain the pattern (->where('slug', '[a-z0-9-]+')).
  • Stored XSS is the number one risk. The body is intended as HTML, so {!! $page->body !!} is correct — which means the editor is the trust boundary. Confirm admin routes are behind LiteCMS ACL/Gate middleware; a middleware refactor that drops that check silently turns your CMS into an XSS vector.
  • Authorization on the admin CRUD. Verify with a request test as a non-admin user against each admin route. Do not assume the provider gated them.
  • Mass assignment. Check $fillable before any Page::create($request->all()) — you do not want published or template settable from an untrusted request.
  • Migrations are not re-runnable. Package migrations typically use Schema::create. Do not re-run them on an existing DB; if you need schema changes, add your own migration rather than editing the vendor one.
  • Slug uniqueness vs soft deletes. If the model uses SoftDeletes, the unique index on slug may still block reuse after deletion. Check the migration.
  • Version constraints are the real blocker. Read composer.json before anything else: which Laravel major and PHP version it pins determines whether you can upgrade your framework at all. A ^10.0 pin will block your own Laravel 11 upgrade.
  • Debugging tips. php artisan route:list --path=page, php artisan vendor:publish --list for tags, php artisan tinker with Page::first() to confirm the schema matches the model's $casts, and Log::info(...) in your repository binding to confirm the container resolves your seam not the vendor class.
  • Extension points to prefer. Model events (saved/deleted) for cache invalidation, the repository contract for swapping implementations, and published Blade views for presentation. Avoid editing vendor source; if you must, fork to a private repo with a named owner and CI.
  • Exit cost. Because the model is your content foundation, keep the PageData DTO free of vendor types so a future native implementation only has to satisfy the interface, not replicate the schema.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings