Docs LATEST

Page Service

Load, save, list, and manage content pages.

Content & Data

PageService is the central API for working with content. It handles caching, events, and coordinates with the underlying content driver.

Getting the Service #

$pages = app('pages');
// or
$pages = app(PageService::class);

Loading Pages #

Single Page #

use VelvetCMS\Exceptions\NotFoundException;

try {
    $page = $pages->load('about');
    echo $page->title;
    echo $page->html();  // Rendered HTML
} catch (NotFoundException $e) {
    // Page does not exist
}

Returns a Page object. Throws NotFoundException if the page doesn't exist.

With Caching #

Pages are cached automatically. The cache is invalidated when content is saved.

// First call: loads from driver, caches result
$page = $pages->load('about');

// Subsequent calls: served from cache
$page = $pages->load('about');

Listing Pages #

Basic List #

$allPages = $pages->list();

Returns a Collection of Page objects.

With Filters #

$published = $pages->list([
    'status' => 'published',
]);

$drafts = $pages->list([
    'status' => 'draft',
]);

Sorting #

$pages->list([
    'order_by' => 'created_at',
    'order_dir' => 'desc',
]);

$pages->list([
    'order_by' => 'title',
    'order_dir' => 'asc',
]);

Note: The FileDriver ignores order_by and order_dir filters โ€” it always sorts by createdAt descending. These filters are only respected by the DBDriver and HybridDriver.

Pagination #

// Get first 10 pages
$firstPage = $pages->list([
    'limit' => 10,
    'offset' => 0,
]);

// Get next 10 pages
$secondPage = $pages->list([
    'limit' => 10,
    'offset' => 10,
]);

Combined Filters #

$recentPublished = $pages->list([
    'status' => 'published',
    'order_by' => 'created_at',
    'order_dir' => 'desc',
    'limit' => 5,
]);

Saving Pages #

use VelvetCMS\Models\Page;

$page = new Page(
    slug: 'new-post',
    title: 'My New Post',
    content: '# Hello\n\nThis is my new post.',
    status: 'draft',
    layout: 'blog',
);

$pages->save($page);

Required Fields #

  • slug - unique identifier/URL
  • title - page title
  • content - page content

Updating Existing Pages #

Save with the same slug to update:

$page = $pages->load('existing-page');
$page->title = 'Updated Title';
$page->status = 'published';

$pages->save($page);

Deleting Pages #

use VelvetCMS\Exceptions\NotFoundException;

try {
    $pages->delete('old-page');
} catch (NotFoundException $e) {
    // Page did not exist
}

Returns true on success. Throws NotFoundException if the page doesn't exist.

Events #

PageService fires events for each operation:

Event Payload When
page.loading string $slug Before loading
page.loaded Page $page After loading
page.saving Page $page Before saving
page.saved Page $page After saving
page.deleting string $slug Before deleting
page.deleted string $slug After deleting

Example: Auto-Generate Slug #

$events->listen('page.saving', function (Page $page) {
    if (empty($page->slug)) {
        $page->slug = slugify($page->title);
    }
});

Example: Clear Cache on Save #

$events->listen('page.saved', function (Page $page) {
    app('cache.tags')->flush('pages');
});

Page Data Structure #

A Page object has the following properties:

use VelvetCMS\Models\Page;

$page = new Page(
    slug: 'about',
    title: 'About Us',
    content: '# About Us\n\nOriginal markdown...',
    status: 'published',
    layout: 'default',
    excerpt: 'Learn more about our company.',
    meta: [],                   // Custom frontmatter fields
    createdAt: new DateTime(),
    updatedAt: new DateTime(),
    publishedAt: new DateTime(),
);

$page->html();                  // Rendered HTML
$page->isPublished();           // true
$page->getExcerpt(160);         // Auto-generated excerpt if not set
$page->getMeta('key');           // Access custom meta fields
$page->toArray();               // Convert to array representation

Controller Example #

class PageController
{
    public function __construct(
        private readonly PageService $pages,
        private readonly ViewEngine $view
    ) {}
    
    public function show(Request $request, string $slug): Response
    {
        try {
            $page = $this->pages->load($slug);
        } catch (NotFoundException $e) {
            return Response::notFound();
        }
        
        if (!$page->isPublished()) {
            return Response::notFound();
        }
        
        return Response::html(
            $this->view->render($page->layout ?? 'default', ['page' => $page])
        );
    }
    
    public function index(Request $request): Response
    {
        $pages = $this->pages->list([
            'status' => 'published',
            'order_by' => 'created_at',
            'order_dir' => 'desc',
            'limit' => 20,
        ]);
        
        return Response::html(
            $this->view->render('pages/index', ['pages' => $pages])
        );
    }
}