Artisan & Setup
php artisan make:livewire MyComponentGenerate component class + Blade viewphp artisan make:livewire --test MyComponentGenerate component with Pest test filephp artisan make:livewire pages/DashboardGenerate in subdirectory (pages/dashboard.blade.php)php artisan make:livewire --inline MyComponentGenerate component with inline view (no separate Blade)php artisan livewire:publish --configPublish Livewire config to config/livewire.phpphp artisan livewire:publish --assetsPublish Livewire JS assetsphp artisan livewire:upgradeUpgrade v2 components to v3 syntax@livewire('my-component')Mount a component in a Blade view@livewire('my-component', ['param' => $value])Mount with initial parameters<livewire:my-component />Mount using tag syntax<livewire:my-component :param="$value" />Mount with parameter via tag syntax<livewire:my-component key="unique-key" />Force fresh instance with key attribute@livewireStylesInject Livewire CSS (place in )@livewireScriptsInject Livewire JS (place before )Component Class
use Livewire\Component;Import base component classuse Livewire\Attributes\Locked;Import #[Locked] attributeuse Livewire\Attributes\Computed;Import #[Computed] attributeuse Livewire\Attributes\Url;Import #[Url] query-string attributeuse Livewire\Attributes\Session;Import #[Session] attributeuse Livewire\Attributes\Modelable;Import #[Modelable] attributeuse Livewire\Attributes\On;Import #[On] event listener attributeuse Livewire\Attributes\Lazy;Import #[Lazy] lazy-load attributeuse Livewire\Attributes\Validate;Import #[Validate] attributeclass MyComponent extends ComponentDeclare a Livewire componentpublic string $name = '';Public string property (synced to template)public int $count = 0;Public integer propertypublic bool $active = false;Public boolean propertypublic array $items = [];Public array propertypublic ?Model $post = null;Public Eloquent model property#[Locked] public int $id;Prevent client from modifying property#[Computed] public function posts()Cached computed property (accessed as $this->posts)#[Url] public string $search = '';Sync property to URL query string#[Url(as: 'q')] public string $search = '';Sync to query string with custom alias#[Session] public string $tab = 'general';Persist property value in session#[Modelable] public string $value = '';Allow parent to bind via wire:modelpublic function mount(int $id): voidRuns once on first render; receives component paramspublic function render(): ViewReturn the component's Blade viewpublic function boot(): voidRuns before every request (use for DI or setup)public function booted(): voidRuns after boot() on every requestActions
public function save(): voidPublic method callable from templatewire:click="save"Call action on clickwire:click="increment"Call increment() on clickwire:click="delete($item->id)"Call action with parameterwire:click="delete({{ $item->id }})"Pass PHP-evaluated param to actionwire:click.prevent="submit"Call action and prevent default browser behaviorwire:click.stop="close"Call action and stop event propagationwire:submit="save"Call action on form submitwire:submit.prevent="save"Call action on submit, prevent page reloadwire:keydown.enter="save"Call action when Enter key is pressedwire:keydown.escape="close"Call action when Escape key is pressedwire:keydown.tab="next"Call action on Tab keydown$this->redirect('/dashboard')Redirect to URL from action$this->redirectRoute('dashboard')Redirect to named route from action$this->redirectRoute('posts.show', $post)Redirect to named route with modelreturn $this->redirect('/')->with('status', 'Saved')Redirect with flash message$this->dispatch('post-created', id: $post->id)Dispatch event from action$this->dispatch('event')->to(OtherComponent::class)Dispatch to specific component class$this->dispatch('event')->self()Dispatch only to self (same component)Properties & Data Binding
wire:model="name"Bind input to property (deferred by default in v3)wire:model.live="name"Update property on every input eventwire:model.live.debounce.500ms="search"Live binding with 500ms debouncewire:model.live.debounce.300ms="query"Live binding with 300ms debouncewire:model.blur="name"Update property when input loses focuswire:model.lazy="name"Update property on change eventpublic int $age = 0PHP type hint auto-casts bound value to intpublic bool $active = falsePHP type hint auto-casts bound value to boolwire:model="address.street"Bind to nested array keywire:model="todos.{{ $index }}.label"Bind to indexed array element$wire.nameRead property value from Alpine.js$wire.set('name', value)Set property value from Alpine.js$wire.get('name')Get property value from Alpine.js$wire.$watch('name', cb)Watch for property changes in Alpine.jsEvents
#[On('post-created')] public function handlePost()Listen for event on method#[On('post-created')] public function handle(int $id)Listener receiving event data$this->dispatch('post-created')Dispatch event to all listeners$this->dispatch('post-created', id: $post->id)Dispatch event with named data$this->dispatch('post-created', data: ['id' => $post->id])Dispatch with data array$this->dispatch('event')->to(OtherComponent::class)Target event to specific component class$this->dispatch('event')->to('other-component')Target event to component by name$this->dispatch('event')->self()Dispatch event only to current component$this->dispatchTo('other-component', 'refresh')Dispatch to component by name (deprecated in v3; prefer ->to() chain)$this->dispatch('browser-event')Dispatch Livewire event (received by components and JS via Livewire.on())@post-created.window="handler"Listen for Livewire browser event in AlpineLivewire.on('event-name', (data) => {})Listen to Livewire event in JavaScript (v3)Livewire.dispatch('event', { id: 1 })Dispatch event from plain JavaScriptLifecycle Hooks
mount()Runs once on initial render; receives component parametersboot()Runs at the start of every component requestbooted()Runs after boot() on every requesthydrate()Runs at start of each subsequent request (after mount)dehydrate()Runs at end of every request before response is sentupdating($name, $value)Runs before any property is updatedupdated($name, $value)Runs after any property is updatedupdatingName($value)Runs before $name property is updated (camelCase)updatedName($value)Runs after $name property is updated (camelCase)updatingAddress($value, $key)Runs before nested array property updaterendering()Runs before render() is calledrendered($view)Runs after render() returns a viewexception($e, $stopPropagation)Handle component exception; call $stopPropagation() to suppressValidation
#[Validate('required|min:3')] public string $nameInline validation rule on property#[Validate('required|email')] public string $emailEmail validation rule on property#[Validate(['name' => 'required|min:3', 'email' => 'email'])]Multi-field rules on class#[Validate('required', message: 'Name is required')] public string $nameCustom validation message on property$this->validate()Validate all properties with their rules$this->validate(['name' => 'required'])Validate with ad-hoc rules$this->validateOnly('name')Validate a single property (e.g. for real-time feedback)$this->addError('name', 'Custom message')Manually add a validation error$this->resetErrorBag()Clear all validation errors$this->resetValidation('name')Clear validation error for one property$errors->has('name')Check if a validation error exists in template@error('name') {{ $message }} @enderrorRender validation error message in templateuse Livewire\WithFileUploads;Add file upload support to componentpublic ?TemporaryUploadedFile $photo = null;Typed file upload property#[Validate(['photo' => 'image|max:1024'])]Validate uploaded file (1 MB max)<input type="file" wire:model="photo">File input bound to upload property$this->photo->store(path: 'photos', disk: 'public')Store uploaded file to diskwire: Directives
wire:clickTrigger action on clickwire:submitTrigger action on form submitwire:modelTwo-way bind input to property (deferred)wire:model.liveTwo-way bind with live updates on inputwire:model.blurTwo-way bind; updates on blurwire:model.lazyTwo-way bind; updates on changewire:model.live.debounce.XmsDebounce live updates (requires .live)wire:keydownTrigger action on keydownwire:keydown.enterTrigger action on Enter keywire:keydown.escapeTrigger action on Escape keywire:keyupTrigger action on keyupwire:changeTrigger action on change eventwire:inputTrigger action on input eventwire:mouseenterTrigger action on mouseenterwire:mouseleaveTrigger action on mouseleavewire:focusTrigger action on focuswire:blurTrigger action on blurwire:loadingShow element while a request is in-flightwire:loading.attr="disabled"Add attribute while loadingwire:loading.class="opacity-50"Add class while loadingwire:loading.removeRemove element while loadingwire:target="save"Scope loading state to a specific actionwire:dirtyShow element when property is modified but not yet savedwire:dirty.class="border-yellow-500"Apply class when input is dirtywire:dirty.remove.class="hidden"Remove class when input is dirtywire:confirm="Are you sure?"Show browser confirm dialog before firing actionwire:navigateSPA-style navigation (no full page reload)wire:navigate.hoverPrefetch page on hover then navigate on clickwire:transitionAnimate element changes with default transitionwire:pollRefresh component every 2s (default)wire:poll.5sRefresh component every 5 secondswire:poll.750msRefresh component every 750mswire:poll.visibleOnly poll when element is visible in viewportwire:offlineShow element when browser is offlinewire:offline.class="opacity-50"Apply class when browser is offlinewire:stream="output"Stream content into element from $this->stream()Lazy Loading
#[Lazy]Attribute on component class to enable lazy loading<livewire:my-component lazy />Enable lazy loading from template<livewire:my-component :lazy="$condition" />Conditionally lazy-load componentpublic function placeholder(): ViewReturn a placeholder view shown before full renderreturn view('livewire.placeholders.spinner')Return spinner view as placeholder<x-slot name="placeholder">...</x-slot>Inline placeholder using named slot// mount() deferredmount() runs on lazy hydration request, not initial page loadRequest fired after page loadFull component loads via separate background XHR#[Lazy(isolate: true)]Isolate lazy requests so they run independentlyAlpine.js Integration
$wireAlpine magic that gives access to the Livewire component$wire.nameRead a Livewire property from Alpine$wire.set('name', value)Set a Livewire property from Alpine$wire.get('name')Get a Livewire property value from Alpine$wire.call('methodName', ...args)Call a Livewire method from Alpine$wire.methodName(args)Shorthand: call Livewire method from Alpine$wire.commit()Force a Livewire network request from Alpine$wire.$watch('name', cb)Watch a Livewire property for changes in Alpine@entangle('open')Two-way Alpine ↔ Livewire property binding@entangle('open').liveEntangle with immediate server syncx-data="{ open: $wire.entangle('open') }"Entangle inside x-data objectLivewire.on('event', callback)Listen for Livewire events in plain JSLivewire.dispatch('event', data)Dispatch Livewire event from plain JSLivewire.hook('request', cb)Hook into Livewire lifecycle from JSLivewire.start()Manually initialise Livewire (when not using auto-inject)@this.nameLegacy v2 syntax; prefer $wire in v3Testing
use Livewire\Livewire;Import the Livewire test helperLivewire::test(MyComponent::class)Create a testable component instanceLivewire::test(MyComponent::class, ['id' => 1])Create component with initial parameters->set('name', 'Alice')Set a property value->call('save')Call an action method->call('delete', $id)Call action with arguments->assertSee('Alice')Assert text is present in rendered output->assertDontSee('Error')Assert text is absent from rendered output->assertSeeHtml('<strong>Alice</strong>')Assert raw HTML is present in output->assertSet('name', 'Alice')Assert property equals expected value->assertNotSet('name', 'Bob')Assert property does not equal value->assertHasErrors(['name'])Assert validation errors exist for field->assertHasErrors(['name' => 'required'])Assert specific validation rule failed->assertHasNoErrors()Assert no validation errors exist->assertHasNoErrors(['name'])Assert no errors for a specific field->assertDispatched('post-created')Assert event was dispatched->assertDispatched('post-created', id: 1)Assert event dispatched with data->assertNotDispatched('post-created')Assert event was not dispatched->assertRedirect('/dashboard')Assert redirect was triggered->assertRedirectToRoute('dashboard')Assert redirect to named route->assertStatus(200)Assert HTTP response status->assertViewIs('livewire.my-component')Assert correct view is used->assertViewHas('posts')Assert view data variable exists->assertUnauthorized()Assert a 401 authorization error->assertForbidden()Assert a 403 forbidden errorLivewire::actingAs($user)Authenticate a user for Livewire testsPagination
use Livewire\WithPagination;Add pagination trait to componentPost::paginate(10)Paginate Eloquent query in component{{ $posts->links() }}Render pagination links in Blade template{{ $posts->links('vendor.pagination.tailwind') }}Render links with custom view$this->resetPage()Reset to page 1 (call on search/filter changes)WithPagination + #[Url]WithPagination handles URL page binding automatically; do not redeclare $page manuallyuse Livewire\WithPagination; // supports cursorWithPagination also supports cursor paginationPost::cursorPaginate(10)Use cursor-based pagination (faster on large tables)$this->resetPage('cursor')Reset to page 1 (pass custom pageName if using non-default page name)'pagination' => 'custom-pagination'Set default pagination view in config/livewire.php