Docs LATEST

Content Drivers

Contract and implementations for content storage backends.

Services

Namespace: VelvetCMS\Contracts\ContentDriver


ContentDriver Interface #

Defines the storage contract for pages. VelvetCMS ships with File, DB, Hybrid, and Auto drivers.

Definition #

interface ContentDriver
{
    /**
     * @throws \VelvetCMS\Exceptions\NotFoundException
     */
    public function load(string $slug): Page;

    /**
     * @throws \VelvetCMS\Exceptions\ValidationException
     */
    public function save(Page $page): bool;

    public function list(array $filters = []): Collection;

    public function paginate(int $page = 1, int $perPage = 20, array $filters = []): Collection;

    /**
     * @throws \VelvetCMS\Exceptions\NotFoundException
     */
    public function delete(string $slug): bool;

    public function exists(string $slug): bool;

    public function count(array $filters = []): int;
}

Methods #

load() #

Load a page by its slug.

public function load(string $slug): Page
Parameter Type Description
$slug string The page slug

Returns: Page instance

Throws: NotFoundException if page doesn't exist

Example:

$driver = app('content.driver');

try {
    $page = $driver->load('welcome');
    echo $page->title;
} catch (NotFoundException $e) {
    // Handle missing page
}

save() #

Save a page (create or update).

public function save(Page $page): bool
Parameter Type Description
$page Page The page to save

Returns: true on success

Throws: ValidationException if page data is invalid

Example:

$page = new Page();
$page->slug = 'new-page';
$page->title = 'New Page';
$page->content = '# Hello World';
$page->status = 'draft';

$driver->save($page);

list() #

Get all pages matching filters.

public function list(array $filters = []): Collection
Parameter Type Description
$filters array Optional filter criteria

Returns: Collection of Page objects

Example:

// All pages
$pages = $driver->list();

// Published pages only
$published = $driver->list(['status' => 'published']);

// With ordering
$recent = $driver->list([
    'order_by' => 'updated_at',
    'order' => 'desc',
    'limit' => 10,
]);

paginate() #

Get paginated pages.

public function paginate(int $page = 1, int $perPage = 20, array $filters = []): Collection
Parameter Type Default Description
$page int 1 Page number
$perPage int 20 Items per page
$filters array [] Filter criteria

Returns: Collection of Page objects

Example:

$pages = $driver->paginate(2, 15, ['status' => 'published']);

delete() #

Delete a page by slug.

public function delete(string $slug): bool
Parameter Type Description
$slug string The page slug

Returns: true on success

Throws: NotFoundException if page doesn't exist

Example:

$driver->delete('old-page');

exists() #

Check if a page exists.

public function exists(string $slug): bool

Example:

if ($driver->exists('about')) {
    $page = $driver->load('about');
}

count() #

Count pages matching filters.

public function count(array $filters = []): int

Example:

$total = $driver->count();
$published = $driver->count(['status' => 'published']);

Filters #

All drivers accept these standard filters:

Filter Type Description
status string 'draft' or 'published'
order_by string Field to sort by (created_at, updated_at, title)
order string 'asc' or 'desc'
limit int Maximum results
offset int Skip first N results

Example:

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

Built-in Drivers #

FileDriver #

Stores pages as files in user/content/pages/.

Namespace: VelvetCMS\Drivers\Content\FileDriver

Features:

  • Loads .vlt files first, then .md
  • Uses YAML frontmatter for metadata
  • Maintains a lightweight index at storage/cache/file-driver-index.json
  • Index auto-updates when file mtime changes

File Structure:

---
title: Welcome
status: published
created_at: 2026-01-01
---

# Welcome to VelvetCMS

Your content here...

Configuration:

// config/content.php
'driver' => 'file',
'drivers' => [
    'file' => [
        'path' => content_path('pages'),
    ],
],

DBDriver #

Stores pages in the pages database table.

Namespace: VelvetCMS\Drivers\Content\DBDriver

Features:

  • Full metadata and content in database
  • Efficient queries and filtering
  • Supports complex search operations

Configuration:

// config/content.php
'driver' => 'db',

HybridDriver #

Stores metadata in database, content in files.

Namespace: VelvetCMS\Drivers\Content\HybridDriver

Features:

  • Fast metadata queries via database
  • Large content stored efficiently in files
  • Best of both worlds

Configuration:

// config/content.php
'driver' => 'hybrid',
'drivers' => [
    'hybrid' => [
        'path' => content_path('pages'),
    ],
],

AutoDriver #

Automatically selects driver based on page count.

Namespace: VelvetCMS\Drivers\Content\AutoDriver

Features:

  • Uses FileDriver for small sites
  • Switches to HybridDriver above threshold

Configuration:

// config/content.php
'driver' => 'auto',
'drivers' => [
    'auto' => [
        'threshold' => 100, // Switch at 100 pages
    ],
],

Creating a Custom Driver #

Implement the ContentDriver interface:

use VelvetCMS\Contracts\ContentDriver;
use VelvetCMS\Models\Page;
use VelvetCMS\Database\Collection;
use VelvetCMS\Exceptions\NotFoundException;

class ApiDriver implements ContentDriver
{
    public function __construct(
        private readonly HttpClient $client,
        private readonly string $baseUrl
    ) {}

    public function load(string $slug): Page
    {
        $response = $this->client->get("{$this->baseUrl}/pages/{$slug}");

        if ($response->status() === 404) {
            throw new NotFoundException("Page '{$slug}' not found");
        }

        return Page::fromArray($response->json());
    }

    public function save(Page $page): bool
    {
        $response = $this->client->put(
            "{$this->baseUrl}/pages/{$page->slug}",
            $page->toArray()
        );

        return $response->successful();
    }

    public function list(array $filters = []): Collection
    {
        $response = $this->client->get("{$this->baseUrl}/pages", $filters);
        $pages = array_map(
            fn($data) => Page::fromArray($data),
            $response->json()
        );

        return new Collection($pages);
    }

    public function paginate(int $page = 1, int $perPage = 20, array $filters = []): Collection
    {
        return $this->list(array_merge($filters, [
            'page' => $page,
            'per_page' => $perPage,
        ]));
    }

    public function delete(string $slug): bool
    {
        $response = $this->client->delete("{$this->baseUrl}/pages/{$slug}");

        if ($response->status() === 404) {
            throw new NotFoundException("Page '{$slug}' not found");
        }

        return $response->successful();
    }

    public function exists(string $slug): bool
    {
        $response = $this->client->head("{$this->baseUrl}/pages/{$slug}");
        return $response->status() === 200;
    }

    public function count(array $filters = []): int
    {
        $response = $this->client->get("{$this->baseUrl}/pages/count", $filters);
        return $response->json()['count'] ?? 0;
    }
}

Register Custom Driver #

// In a service provider
$app->bind('content.driver', function($app) {
    return new ApiDriver(
        $app->make(HttpClient::class),
        config('content.drivers.api.url')
    );
});

Usage with PageService #

The PageService wraps content drivers with caching and events:

// Direct driver access (no caching)
$driver = app('content.driver');
$page = $driver->load('welcome');

// Via PageService (with caching and events)
$pages = app('pages');
$page = $pages->load('welcome'); // Cached for 300s

See PageService for the recommended high-level API.