A Filament plugin to add a preview screen to your pages using websockets. The screen will render your website with the data from Filament, without saving it. This is heavily based on Filament Peek.
You can install the package via composer:
composer require wotz/filament-live-previewYou can publish the config file with:
php artisan vendor:publish --tag="filament-live-preview-config"This is the contents of the published config file:
return [
];Optionally, you can publish the views using
php artisan vendor:publish --tag="filament-live-preview-views"Follow the Laravel Reverb installation instructions here
In bootstrap/app.php, add the following lines:
return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'livewire/*',
]);
})Since we reload Livewire via websockets, we need to exclude the Livewire routes from CSRF protection. Else you will get a 419 HTTP status code when Livewire tries to make requests.
use Filament\Schemas\Components\Section;public static function form(Schema $schema): Schema
{
return $schema
->columns(3)
->components([
Section::make()
->schema([
// Your fields here
])
->columnSpan(function (Component $livewire) {
return ['lg' => $livewire->isPreviewing() ? 1 : 3];
})
->extraAttributes([
'data-live-preview-form' => '',
]),
View::make('filament-live-preview::components.live-preview-frame')
->hidden(fn (Component $livewire) => ! $livewire->isPreviewing())
->columnSpan(['lg' => 2]),
]);
}Add the HasLivePreviewComponent trait and the bundled preview action group to your header actions. The group exposes two entries: Open in sidebar (toggles the inline preview pane) and Open in new tab (opens the preview in a separate browser tab so it can be placed side-by-side on a second monitor).
use Filament\Resources\Pages\EditRecord;
use Wotz\FilamentLivePreview\Filament\Traits\HasLivePreviewComponent;
class EditPage extends EditRecord
{
use HasLivePreviewComponent;
protected function getHeaderActions(): array
{
return [
$this->getLivePreviewAction(),
$this->getSaveFormAction()->submit(null)->action('save'),
DeleteAction::make(),
];
}
protected function getPreviewModalView(): ?string
{
// This corresponds to resources/views/posts/preview.blade.php
return 'page.show';
}
protected function getPreviewModalDataRecordKey(): ?string
{
return 'page';
}
protected function mutatePreviewModalData(array $data): array
{
$data['titleForLayout'] = $data['page']->title;
return $data;
}
}If you only want one of the two entries, call toggleIsPreviewing() (sidebar) or openPreviewInNewTab() directly from a custom action. For the new-tab variant, add ->extraAttributes(['data-live-preview-open-tab' => true]) to your action — the package's JS listens for that attribute and pre-opens a blank tab during the click, which is required to bypass popup blockers.
See the Peek docs for more details on how to customize the preview data.
use Filament\Support\Enums\Width;
use Pboivin\FilamentPeek\FilamentPeekPlugin;
use Wotz\FilamentLivePreview\LivePreviewPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
// Your other plugins...
FilamentPeekPlugin::make() // Disable styles and scripts for peek, since we override them in our package
->disablePluginStyles()
->disablePluginScripts(),
LivePreviewPlugin::make(),
])
->maxContentWidth(Width::Full) // This is optional, but makes that your panel uses the full width of the screen
;
}Since we use a full page Livewire component to render the preview, and you use a Blade component for layout, you can make some changes to
In our projects we have this AppLayout component:
<?php
namespace App\View\Components;
use App\Models\Page;
use App\Models\StaticPage;
use Closure;
use Codedor\Seo\Facades\SeoBuilder;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class AppLayout extends Component
{
public function __construct(
public string $titleForLayout,
) {
$this->renderDiv = request()->routeIs('live-preview-frame') || request()->is('livewire/update');
}
public function render(): View|Closure|string
{
if ($this->renderDiv) {
return <<<'blade'
<div>
{{ $slot }}
</div>
blade;
}
return view('layouts.app');
}
}This make sure that the view defined in getPreviewModalView is wrapped in a <div> instead of the full layout, when rendering the live preview component.
In routes/channels.php, add the following line:
Broadcast::channel('live-preview', function () {});If you load a blade file which loads a Livewire component, it wil break the live preview for some unknown reason.
This can be fixed by wrapping the Livewire component in a conditional, like this:
@if (! is_live_preview_request())
<livewire:filters />
@else
{{ __('filament-live-preview::alert.preview unavailable for :name', [
'name' => "filters"
]) }}
@endif