Skip to content

Content Resolution

Content resolution is the process of transforming raw JSON from the database into structured, typed objects that frontend components can consume.

graph LR
    A[Database JSON] -->|ContentResolver| B[SectionContent]
    B -->|__get| C[BlockData]
    B -->|all| D["BlockData[]"]
    C -->|__get| E[Field Value]
    C -->|getSourceMap| F[CmsSourceMap]

In the database, a section’s content column stores flat JSON:

[
{
"type": "hero",
"data": {
"label": "Welcome",
"headline": "Build Amazing Sites",
"primary_cta_text": "Get Started",
"primary_cta_url": "/contact"
}
},
{
"type": "paragraph",
"data": {
"content": "We create digital experiences..."
}
}
]

Important: Data uses flat string keys, NOT nested arrays.

JFA\FilamentCMSCore\CMS\ContentResolver transforms this JSON:

use JFA\FilamentCMSCore\CMS\ContentResolver;
use JFA\FilamentCMSCore\Models\Section;
$section = Section::find(1);
$content = app(ContentResolver::class)->resolve($section, pageId: 5);
// Returns: SectionContent

The resolver:

  1. Decodes the JSON content array
  2. Groups blocks by their type
  3. Wraps each block in BlockData
  4. Attaches source map context (section_id, page_id, block_index)
  5. Extracts images from Spatie Media Library
  6. Returns a SectionContent instance

JFA\FilamentCMSCore\CMS\SectionContent is the main container:

$content = $section->resolveContent();
// Get first block of a type (returns BlockData or NullBlockData)
$hero = $content->hero;
$paragraph = $content->paragraph;
// Check if a type exists
if ($content->has('hero')) {
// ...
}
// Get all blocks of a type
$allParagraphs = $content->all('paragraph');
// Get all blocks for iteration
foreach ($content->allBlocks() as $block) {
echo $block->type;
}
// Access images from Spatie Media Library
$images = $content->images;

JFA\FilamentCMSCore\CMS\BlockData wraps a single block:

$hero = $content->hero;
// Field access via magic __get
$label = $hero->label;
$headline = $hero->headline;
// Check field existence
if ($hero->has('primary_cta_text')) {
// ...
}
// Convert to array
$array = $hero->toArray();
// Get source map (for visual editing)
$sourceMap = $hero->getSourceMap();

When a block type doesn’t exist, NullBlockData is returned instead of null. This prevents errors:

// Safe — returns empty string instead of throwing error
$label = $content->nonexistent->label ?? '';
// Safe — returns false
$hasField = $content->nonexistent->has('field');

During resolution, source maps are attached to each BlockData:

// ContentResolver injects:
$blockData = $blockData->withSourceMap([
'sectionId' => $section->id,
'sectionSlug' => $section->slug,
'blockType' => $block['type'],
'blockIndex' => $index,
'pageId' => $pageId,
]);

This provenance metadata is what enables visual editing — it tells the system exactly which database record each rendered HTML element came from.

namespace App\Livewire;
use JFA\FilamentCMSCore\CMS\SectionContent;
use JFA\FilamentCMSLivewire\Livewire\SectionComponent;
class Hero extends SectionComponent
{
public string $label = '';
public string $headline = '';
public string $primaryCtaText = '';
public string $primaryCtaUrl = '';
public array $cmsSourceMap = [];
protected function hydrateFromContent(SectionContent $content): void
{
// Content resolution happens automatically in mount()
// $content->hero returns BlockData (or NullBlockData)
$this->label = $content->hero->label ?? '';
$this->headline = $content->hero->headline ?? '';
$this->primaryCtaText = $content->hero->primary_cta_text ?? '';
$this->primaryCtaUrl = $content->hero->primary_cta_url ?? '';
}
protected function initializeVisualEditing(): void
{
// Source map is available for visual editing
$sourceMap = $this->sectionContent->hero->getSourceMap();
if ($sourceMap !== null) {
$this->cmsSourceMap = $sourceMap;
}
}
protected function getCmsSourceMap(): array
{
return $this->cmsSourceMap;
}
// resolveFieldValue is only needed when using InteractsWithVisualEditing trait
protected function resolveFieldValue(string $field): mixed
{
return match ($field) {
'label' => $this->label,
'headline' => $this->headline,
'primary_cta_text' => $this->primaryCtaText,
'primary_cta_url' => $this->primaryCtaUrl,
default => null,
};
}
}

The abstract Page class handles resolution automatically:

// In JFA\FilamentCMSLivewire\Livewire\Page::render()
$page = \JFA\FilamentCMSCore\Models\Page::bySlug($this->slug());
$sections = $page->sectionsForUI();
// sectionsForUI() returns an array of:
// [
// 'component' => 'App\Livewire\Hero',
// 'content' => SectionContent,
// ]
  • Page slug cachingPage::bySlug() caches lookups
  • Eager loading — The Page model eager-loads sections and their pivot data
  • Image conversions — Spatie Media Library handles image resizing