cortex/pages
Cortex Pages is a Laravel package for managing structured, hierarchical page content with routing, templates, and SEO metadata, giving applications a flexible foundation for editable site pages and navigation.
cortex/pages (repo rinvex/cortex-pages-classic) is not a standalone package — it is a module of the Rinvex Cortex CMS and assumes cortex/foundation is already installed. Do not try to bolt it onto a vanilla Laravel app; the "frontend layer" it provides is a routable entity registration that only cortex/foundation's router understands.
composer require rinvex/cortex-pages-classic
php artisan vendor:publish --tag=cortex-pages # config + views + migrations
php artisan migrate
Look here first, in this order:
src/Providers/PagesServiceProvider.php — what it binds, publishes, and registers as routable.src/Models/Page.php + src/Contracts/PageContract.php — the actual attribute surface (uri, title, body, excerpt, view, active, featured).src/Routes/ and the Pageable/routable registration — this is where URLs become pages.resources/views/ — the Blade templates the view column points at.First use case — a static "About" page rendered from the DB:
use Rinvex\Pages\Contracts\PageContract;
$page = app(PageContract::class)->create([
'uri' => 'about-us', // no leading slash
'title' => 'About Us',
'body' => '<p>Hello.</p>',
'view' => 'cortex-pages::default.pages.show',
'active' => true,
]);
Then hit /{tenant?}/about-us. If you get a 404, the router didn't resolve the entity — see Gotchas.
Resolve through the contract, not the model. Cortex layers rinvex/laravel-repositories over Eloquent: validation, caching criteria, and events live in the repository. Inject PageContract (or the repository contract) wherever you create/update pages; use Page directly only for read-side query scopes in Blade/sitemap code.
public function __construct(protected PageContract $pages) {}
public function show(string $uri)
{
return $this->pages->findByUri($uri) ?? abort(404); // repository handles caching
}
Lean on the routable registration rather than hand-rolled routes. The page's uri is the canonical URL; do not add a Route::get('{slug}', ...) fallback, or you'll shadow the module's resolution and break tenant-aware lookups.
Views are data, not code. The view column selects the Blade template per page. That gives editors a template switcher for free:
// app-wide default
'views' => ['default' => 'theme::pages.default', 'landing' => 'theme::pages.landing'],
// per record
$pages->update($page, ['view' => 'theme::pages.landing']);
Publish the package views and override them in your theme namespace — never edit vendor/.
Tenant scoping. In a multi-tenant install the module applies the foundation global scope; set tenant context in middleware before the router runs, otherwise you'll resolve the right URI against the wrong tenant and get a false 404. When writing pages from console commands or seeders, wrap in the tenant context helper, not a bare Page::create().
Translatable + attributes. title/body/excerpt are typically translatable and page-level custom fields hang off cortex/attributes. Treat both as array-shaped: pass locale-keyed arrays on write, and call app()->setLocale() explicitly in jobs/commands (the fallback locale silently masks missing translations).
Integration points worth wiring early:
cortex/menus — store the page uri in menu items so URLs stay authoritative in one place.cortex/media — use attributes/featured for hero images instead of inlining in body./{tenant}/{uri}; this is a 20-line command, don't pull a package.Testing. RefreshDatabase, seed a tenant, create via the repository, then $this->get('/about-us')->assertOk() and a second test asserting an active => false page 404s. The 404 path is where most regressions hide.
Archived and unmaintained — this is the headline. The -classic suffix marks the pre-rewrite Cortex modules, frozen around the Laravel 5.6–6 era and PHP 7.x. Expect friction on Laravel 9+ (service provider signatures, Str/Arr helpers, $dates, container bind semantics). Pin exact versions in composer.json, or budget a fork. There are zero dependents for a reason; do not treat it as a foundation for new work without a maintenance plan.
Verify the composer name. The repo is rinvex/cortex-pages-classic but the package is listed as cortex/pages; run composer show and check composer.json name/autoload before assuming the PSR-4 namespace (Rinvex\Pages\). A wrong require string is the most common install failure.
URI normalization is on you. about-us works; /about-us typically does not. Slugs are unique per tenant — a duplicate uri either throws a validation exception or, worse, silently resolves to the older record. Normalize on write in a form request or repository override.
Inactive pages are soft 404s. active => false means the router won't resolve the entity at all — it doesn't render a "coming soon" view. Don't build preview workflows on top of it; add a separate published_at attribute if you need drafts.
Cache invalidation. The repository caches lookups. Direct DB writes (seeders, DB::table('pages')->update(), queue workers hitting raw SQL) bypass invalidation and leave stale pages live. Always go through the repository, or explicitly flush the page cache key namespace after bulk operations.
Debugging a 404 is not route:list. Dynamic routable URIs never appear there. Step through Cortex\Foundation\Routing\Router::findByUri() (or dump the resolved routable collection), and check in this order: tenant context set → active true → URI exact match → cache not stale.
Extension points, cheapest first: publish and override views; extend Page and rebind PageContract in your own service provider; override the repository binding to add validation/criteria; hook Eloquent model events (saved, deleted) for cache warming and sitemap regeneration. Modifying vendor files is a dead end — the next composer update eats it.
Watch for classic-vs-lite schema drift. If any tutorial or Stack Overflow answer references Cortex Pages without "classic", the column names and route registration may differ from what's in your migrations. Trust the migration file, not the docs.
How can I help you explore Laravel packages today?