Content Resolution
Content resolution is the process of transforming raw JSON from the database into structured, typed objects that frontend components can consume.
The Resolution Pipeline
Section titled “The Resolution Pipeline”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]
How Content is Stored
Section titled “How Content is Stored”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.
ContentResolver
Section titled “ContentResolver”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: SectionContentThe resolver:
- Decodes the JSON content array
- Groups blocks by their
type - Wraps each block in
BlockData - Attaches source map context (section_id, page_id, block_index)
- Extracts images from Spatie Media Library
- Returns a
SectionContentinstance
SectionContent
Section titled “SectionContent”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 existsif ($content->has('hero')) { // ...}
// Get all blocks of a type$allParagraphs = $content->all('paragraph');
// Get all blocks for iterationforeach ($content->allBlocks() as $block) { echo $block->type;}
// Access images from Spatie Media Library$images = $content->images;BlockData
Section titled “BlockData”JFA\FilamentCMSCore\CMS\BlockData wraps a single block:
$hero = $content->hero;
// Field access via magic __get$label = $hero->label;$headline = $hero->headline;
// Check field existenceif ($hero->has('primary_cta_text')) { // ...}
// Convert to array$array = $hero->toArray();
// Get source map (for visual editing)$sourceMap = $hero->getSourceMap();NullBlockData
Section titled “NullBlockData”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');Source Map Injection
Section titled “Source Map Injection”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.
Complete Example
Section titled “Complete Example”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, }; }}How Pages Resolve Content
Section titled “How Pages Resolve Content”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,// ]Performance Considerations
Section titled “Performance Considerations”- Page slug caching —
Page::bySlug()caches lookups - Eager loading — The Page model eager-loads sections and their pivot data
- Image conversions — Spatie Media Library handles image resizing