laravel 7 min read

Livewire 3 and Filament: Building Dynamic Admin Panels Without JavaScript

Livewire 3 and Filament let you build fully reactive admin panels, dashboards, and CRUD interfaces entirely in PHP — no Vue, no React, no API routes required.

G
Gurpreet Singh
March 05, 2026

Why Livewire and Filament Changed How I Build Admin Panels

Before Livewire, every time a client asked for a dynamic admin panel — real-time search, inline editing, dependent dropdowns, sortable tables — I faced the same choice: build a Vue or React SPA with a separate API, or ship a clunky full-page-reload interface and call it done. Neither felt right for the typical Laravel project.

Livewire 3 (released September 2023) and Filament v3 (built on top of Livewire 3) changed the calculus entirely. You write PHP. The browser behaves as if you wrote JavaScript. No context switching, no API design overhead, no separate frontend build pipeline for admin interfaces.

I have built five production admin panels with Filament v3 over the past 18 months — tenant management dashboards, AI chatbot knowledge base managers, content moderation queues, and billing oversight tools. This article covers what I have learned about building them properly.

Livewire 3: How It Works

Livewire works by serialising your PHP component's state, sending it to the browser alongside a rendered HTML snapshot, and then using a small JavaScript runtime to intercept user interactions. When a user clicks a button or types in an input, Livewire sends the updated state back to your Laravel server, re-renders the component, and morphs only the changed DOM nodes — similar to how Vue's virtual DOM diffing works, but driven entirely by your PHP class.

The key improvement in Livewire 3 over v2 is the move from a custom diffing algorithm to Alpine.js as the DOM diffing engine. This makes Livewire 3 significantly faster on large components, and it means Alpine.js directives work seamlessly alongside Livewire — no conflicts.

A Basic Livewire 3 Component

// app/Livewire/PostSearch.php
use Livewire\Component;
use Livewire\Attributes\Computed;

class PostSearch extends Component
{
    public string $query = '';
    public string $category = 'all';

    #[Computed]
    public function posts()
    {
        return Post::published()
            ->when($this->query, fn($q) => $q->where('title', 'like', "%{$this->query}%"))
            ->when($this->category !== 'all', fn($q) => $q->where('category', $this->category))
            ->latest()
            ->paginate(12);
    }

    public function render()
    {
        return view('livewire.post-search');
    }
}
{{-- resources/views/livewire/post-search.blade.php --}}
<div>
    <input wire:model.live.debounce.300ms="query" placeholder="Search posts..." />

    <select wire:model.live="category">
        <option value="all">All Categories</option>
        <option value="laravel">Laravel</option>
        <option value="ai">AI</option>
    </select>

    @foreach($this->posts as $post)
        <div>{{ $post->title }}</div>
    @endforeach

    {{ $this->posts->links() }}
</div>

The wire:model.live.debounce.300ms modifier means the component re-renders 300ms after the user stops typing — no JavaScript event listeners written by you, no API endpoint needed. The #[Computed] attribute caches the property within a single request and ensures pagination links work correctly.

Filament v3: What It Adds on Top of Livewire

Filament is an admin panel framework built entirely on Livewire 3. It provides:

  • A complete CRUD scaffold (Resource classes with list, create, edit, view pages)
  • 50+ pre-built form fields (text, select, rich editor, file upload, repeater, etc.)
  • Pre-built table columns with sorting, filtering, and bulk actions
  • Widgets for dashboards (stats, charts)
  • Notification system
  • Multi-tenancy support built-in
  • A plugin ecosystem with 100+ community plugins

The speed advantage is real. A fully functional CRUD panel for a model — list view with sortable columns, search, filters, create form, edit form, soft delete, export — takes about 30–45 minutes to build in Filament. The equivalent from scratch would take 2–3 days.

Creating a Filament Resource

php artisan make:filament-resource Post --generate

The --generate flag introspects your model's database columns and generates sensible form fields and table columns automatically. You then customise from that starting point:

// app/Filament/Resources/PostResource.php
class PostResource extends Resource
{
    protected static ?string $model = Post::class;
    protected static ?string $navigationIcon = 'heroicon-o-document-text';

    public static function form(Form $form): Form
    {
        return $form->schema([
            Forms\Components\TextInput::make('title')
                ->required()
                ->maxLength(255)
                ->live(onBlur: true)
                ->afterStateUpdated(fn ($state, Forms\Set $set) =>
                    $set('slug', Str::slug($state))
                ),

            Forms\Components\TextInput::make('slug')
                ->required()
                ->unique(ignoreRecord: true),

            Forms\Components\Select::make('category')
                ->options(['laravel' => 'Laravel', 'ai' => 'AI'])
                ->required(),

            Forms\Components\RichEditor::make('body')
                ->columnSpanFull()
                ->required(),

            Forms\Components\Toggle::make('is_published')
                ->reactive()
                ->afterStateUpdated(fn ($state, Forms\Set $set) =>
                    $set('published_at', $state ? now() : null)
                ),

            Forms\Components\DateTimePicker::make('published_at')
                ->visible(fn (Forms\Get $get) => $get('is_published')),
        ]);
    }

    public static function table(Table $table): Table
    {
        return $table
            ->columns([
                Tables\Columns\TextColumn::make('title')->searchable()->sortable(),
                Tables\Columns\BadgeColumn::make('category')
                    ->colors(['primary' => 'laravel', 'success' => 'ai']),
                Tables\Columns\IconColumn::make('is_published')->boolean(),
                Tables\Columns\TextColumn::make('published_at')->dateTime()->sortable(),
            ])
            ->filters([
                Tables\Filters\SelectFilter::make('category')
                    ->options(['laravel' => 'Laravel', 'ai' => 'AI']),
                Tables\Filters\TernaryFilter::make('is_published'),
            ])
            ->actions([
                Tables\Actions\EditAction::make(),
                Tables\Actions\DeleteAction::make(),
            ])
            ->bulkActions([
                Tables\Actions\BulkActionGroup::make([
                    Tables\Actions\DeleteBulkAction::make(),
                ]),
            ]);
    }
}

Building Custom Filament Widgets

The dashboard is where Filament really shines. You can build stat cards, charts, and custom data views as Livewire-powered widgets:

// app/Filament/Widgets/PostStatsWidget.php
class PostStatsWidget extends StatsOverviewWidget
{
    protected function getStats(): array
    {
        return [
            Stat::make('Total Posts', Post::count()),
            Stat::make('Published', Post::published()->count())
                ->description('Live on site')
                ->descriptionIcon('heroicon-m-arrow-trending-up')
                ->color('success'),
            Stat::make('Drafts', Post::unpublished()->count())
                ->color('warning'),
        ];
    }
}

Livewire vs Vue.js: When to Use Each

Livewire is not a replacement for Vue or React in all scenarios. Here is how I decide:

Use Livewire whenUse Vue/React when
Admin panels, CRUD interfaces, dashboardsCustomer-facing SPAs with complex state
Forms with conditional fields, real-time validationHighly interactive UI (drag-and-drop, canvas)
Server-side data that refreshes periodicallyOffline-capable apps (PWAs)
The page is mostly PHP/Blade with interactive islandsThe entire frontend is a SPA with client routing
Team is stronger in PHP than JavaScriptDedicated frontend team with JS expertise

For most Laravel projects I work on — B2B SaaS platforms, internal tools, portfolio sites — Livewire covers 80–90% of the interactivity needs. The remaining 10–20% (usually a rich text editor, a drag-and-drop kanban, or a real-time chat component) gets a targeted Alpine.js or Vue component embedded inside the Livewire page.

Performance Considerations

Livewire makes an HTTP round-trip to the server on every interaction. For most cases this is imperceptible (50–100ms on a well-optimised server). But for high-frequency interactions — character-by-character search, real-time sliders — you need to be deliberate:

  • Use wire:model.live.debounce.300ms instead of wire:model.live for text inputs
  • Use #[Computed(persist: true)] to cache computed properties in the session for expensive queries
  • Use wire:loading directives to show spinners during server round-trips so the UI feels responsive
  • Use wire:key on loop items to prevent Livewire from re-rendering unchanged list items

Frequently Asked Questions

Is Filament suitable for customer-facing interfaces, or only admin panels?

Filament is primarily designed for admin panels — your own team and operators interacting with your data. Using it for customer-facing UI is possible but not recommended: the design language is Tailwind-based and opinionated, customising it to match a unique brand is harder than building a bespoke Livewire interface. For customer-facing dynamic pages, use Livewire directly with your own Blade components. For internal dashboards and CRUD, reach for Filament — it is far faster to build with.

How does Livewire handle file uploads?

Livewire 3 handles file uploads natively via the WithFileUploads trait. Files are uploaded to a temporary directory on your server (or S3 via a signed URL) before the form is submitted, giving you real-time preview and validation without a custom JavaScript uploader. Filament wraps this in a polished file upload component with drag-and-drop, image preview, and multiple file support out of the box.

Does Livewire work well with Laravel Queues?

Yes. A common pattern is to dispatch a queued job from a Livewire component action, then use Livewire's polling (wire:poll.5s) or Laravel Echo with Livewire's $dispatch to update the UI when the job completes. This is how I build AI-processing screens: the user triggers a job, the Livewire component polls for completion, and updates the result without a page reload.

#Livewire #Filament #Laravel #PHP #Admin Panel #Tailwind
G
Gurpreet Singh

Senior Full Stack Developer — Laravel, Vue.js, Nuxt.js & AI. Available for freelance projects.

Hire Me for Your Project

Related Articles