Visual Editing Setup
Visual editing allows authorized users to click on frontend content and edit it inline. This guide shows you how to set it up for your sections.
Prerequisites
Section titled “Prerequisites”-
Install the visual editing package:
Terminal window composer require franc014/ve-filament-cms-livewire -
Run the installer:
Terminal window php artisan ve-filament-cms-livewire:install
How Visual Editing Works
Section titled “How Visual Editing Works”graph TD
A[User clicks Edit button] -->|toggle| B[EditingMode activates]
B -->|page reload| C[DOM instruments data-cms-source]
C -->|user clicks element| D[VisualEditor opens modal]
D -->|loads| E[InlineEditForm]
E -->|saves| F[Database updates]
F -->|emits| G[contentUpdated event]
G -->|refreshes| H[Livewire Component]
Step 1: Add the Trait
Section titled “Step 1: Add the Trait”Add InteractsWithVisualEditing from ve-filament-cms-livewire:
namespace App\Livewire;
use JFA\FilamentCMSLivewire\Livewire\SectionComponent;use JFA\VeFilamentCMSLivewire\Concerns\InteractsWithVisualEditing;
class Hero extends SectionComponent{ use InteractsWithVisualEditing;
public string $label = ''; public string $headline = ''; public array $cmsSourceMap = [];
// autoHydrate() handles $label and $headline automatically
protected function initializeVisualEditing(): void { $block = $this->block?->first(); if ($block !== null) { $this->cmsSourceMap = $block->getSourceMap(); } }
public function render(): \Illuminate\Contracts\View\View { return view('livewire.components.hero'); }}Note: Without this trait,
renderField()is unavailable. Non-visual-editing components simply don’t need it — use{{ $label }}directly.
Step 2: Use renderField() in Blade
Section titled “Step 2: Use renderField() in Blade”renderField() is provided by the InteractsWithVisualEditing trait. When visual editing is active, it injects data-cms-source attributes automatically:
{{-- Before --}}<span>{{ $label }}</span><h1>{{ $headline }}</h1>
{{-- After --}}<span>{!! $this->renderField('label', 'text') !!}</span><h1>{!! $this->renderField('headline', 'text') !!}</h1>Field Types
Section titled “Field Types”Pass the appropriate field type to render the correct editor:
| Type | Editor | Use For |
|---|---|---|
text | TextInput | Short text, headlines |
textarea | Textarea | Medium text, descriptions |
rich_text | RichEditor | Formatted content |
image | FileUpload | Images |
repeater | Repeater | Lists, grids |
Image Fields
Section titled “Image Fields”<img src="{{ $imageUrl }}" alt="{{ $imageAlt }}" data-cms-source="{{ $this->renderImageField('image_url') }}">Repeater Containers
Section titled “Repeater Containers”<div data-cms-source="{{ $this->renderRepeaterContainer('members') }}"> @foreach($members as $member) <div>{{ $member['name'] }}</div> @endforeach</div>Step 3: Include VisualEditor in Layout
Section titled “Step 3: Include VisualEditor in Layout”Add the visual editor component to your main layout:
{{-- resources/views/layouts/app.blade.php --}}<body> {{ $slot }}
@auth @can('EditFrontendContent') <livewire:visual-editor /> @endcan @endauth</body>Or include it unconditionally (the component checks authorization internally):
<livewire:visual-editor />Step 4: Test Visual Editing
Section titled “Step 4: Test Visual Editing”- Log in as a user with
EditFrontendContentpermission - Visit a page with your section
- Click the floating “Edit” button
- Click on editable content
- Edit and save
- Verify changes appear immediately
Field Labels (Auto-Extracted)
Section titled “Field Labels (Auto-Extracted)”Labels are automatically extracted from your ContentBlock schema — no manual fieldLabels() method needed:
// In your ContentBlock:Block::make('hero') ->schema([ TextInput::make('headline') ->label('Headline') // This label is auto-extracted ->required(), ])
// BlockSchemaExtractor reads 'Headline' from the schema// renderField() uses it for visual editing tooltipsTranslation Labels
Section titled “Translation Labels”For localized labels, use translation files with nested arrays:
return [ 'hero' => [ 'fields' => [ 'label' => 'Etiqueta', 'headline' => 'Titular', ], ],];// In your ContentBlock:TextInput::make('headline') ->label(__('cms.hero.fields.headline'))Important: Repeater parent keys MUST use .label suffix:
Repeater::make('key_points') ->label(__('cms.mission.fields.key_points.label')) // .label suffix! ->schema([ TextInput::make('title') ->label(__('cms.mission.fields.key_points.title')), ])How It Works Internally
Section titled “How It Works Internally”EditingMode
Section titled “EditingMode”Session-backed singleton:
$editingMode = app(\JFA\VeFilamentCMSLivewire\EditingMode::class);
$editingMode->activate(); // Enable editing$editingMode->deactivate(); // Disable editing$editingMode->isActive(); // Check stateInitializeEditingMode Middleware
Section titled “InitializeEditingMode Middleware”Auto-registered by VeFilamentCMSLivewireServiceProvider on the web group:
// Ensures EditingMode is resolved earlypublic function handle(Request $request, Closure $next): Response{ $this->editingMode->isActive(); return $next($request);}contentUpdated Event
Section titled “contentUpdated Event”When content is saved:
// InlineEditForm dispatchesdispatch('contentUpdated', sectionId: $sectionId);
// InteractsWithVisualEditing trait listens automatically// refreshFromCMS() re-hydrates the section from the databaserefreshFromCMS Flow
Section titled “refreshFromCMS Flow”sequenceDiagram
participant VE as VisualEditor
participant IEF as InlineEditForm
participant DB as Database
participant SC as SectionComponent
VE->>IEF: Save changes
IEF->>DB: Update section content
IEF->>SC: dispatch('contentUpdated')
SC->>DB: Section::find(sectionId)
DB->>SC: Updated section
SC->>SC: resolve() → BlockCollection
SC->>SC: autoHydrate()
SC->>SC: Re-render with new data
Disabling Visual Editing
Section titled “Disabling Visual Editing”You can disable visual editing via config without uninstalling the package:
'visual_editing' => [ 'enabled' => false,],Or via environment variable:
VISUAL_EDITING_ENABLED=falseWhen disabled, renderField() outputs plain values and the contentUpdated listener is a no-op.
Wire Key for Inline Edit Form
Section titled “Wire Key for Inline Edit Form”Use a dynamic wire:key to force Livewire remount when switching fields:
<livewire:inline-edit-form :source-map="$activeSourceMap" wire:key="inline-edit-{{ md5(json_encode($activeSourceMap)) }}"/>This prevents form schema caching bugs where the wrong field label or type is shown.
Troubleshooting
Section titled “Troubleshooting”| Problem | Cause | Solution |
|---|---|---|
| Edit button not showing | Missing permission | Assign EditFrontendContent role |
| Elements not clickable | Missing renderField() | Use {!! $this->renderField() !!} |
| Wrong field type shown | Missing field_type | Pass second parameter to renderField() |
| Changes not reflecting | Missing source map | Initialize $this->cmsSourceMap in initializeVisualEditing() |
| Labels showing as keys | Missing translations | Add to lang/{locale}/cms.php |
| Modal not opening | Missing wire:key | Add dynamic wire:key on inline-edit-form |
| Content disappears after save | Livewire serialization | Don’t override mount() without calling parent::mount() |