To paginate data in Livewire 4, add the WithPagination trait to your component, return a paginator from a computed property and print the links with $this->posts->links(). Livewire then changes pages without a full reload. This tutorial builds a paginated post list with live search for Laravel 13, and shows how to reset the page when the search text changes.
Targets: Laravel 13 (PHP 8.3 to 8.5) and Livewire 4. In Livewire 4 the documented component style is a single file: PHP class and Blade markup together in one file.
Step 1: Install Livewire
composer require livewire/livewireLivewire needs a layout that loads its assets. If your layout does not include them automatically, follow the installation page of the Livewire documentation.
Step 2: Prepare a Post model with sample data
php artisan make:model Post -mfSchema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->timestamps();
});// database/factories/PostFactory.php
public function definition(): array
{
return [
'title' => fake()->sentence(6),
'body' => fake()->paragraph(),
];
}php artisan migrate
php artisan tinker --execute="App\Models\Post::factory()->count(50)->create();"Step 3: Create the paginated component
Create the file resources/views/components/⚡post-list.blade.php (you can also generate it with php artisan make:livewire post-list). It uses the same structure as the pagination examples in the Livewire 4 documentation:
<?php
use App\Models\Post;
use Livewire\Attributes\Computed;
use Livewire\Component;
use Livewire\WithPagination;
new class extends Component {
use WithPagination;
public string $search = '';
public function updatedSearch(): void
{
$this->resetPage();
}
#[Computed]
public function posts()
{
return Post::query()
->when($this->search !== '', fn ($query) => $query->where('title', 'like', '%'.$this->search.'%'))
->latest()
->paginate(10);
}
};
?>
<div>
<input type="search" wire:model.live.debounce.300ms="search" placeholder="Search posts...">
<ul>
@forelse ($this->posts as $post)
<li wire:key="post-{{ $post->id }}">{{ $post->title }}</li>
@empty
<li>No posts found.</li>
@endforelse
</ul>
{{ $this->posts->links() }}
</div>How does this code work?
WithPaginationmakes Livewire read and write the current page and update the list without a browser reload.#[Computed]turns theposts()method into a property you read as$this->posts. The query result is cached for the single request, so the template can use it several times without extra queries.paginate(10)returns a normal Laravel paginator, solinks()renders the usual page links.updatedSearch()is a lifecycle hook that runs when$searchchanges. CallingresetPage()there sends the user back to page 1.wire:model.live.debounce.300msupdates the search property while typing, waiting 300 milliseconds after the last key press so you do not query on every letter.wire:keygives each list item a stable identity, which helps Livewire update the DOM correctly.
Step 4: Show the component on a page
<livewire:post-list />Put the tag in any Blade view, for example resources/views/posts.blade.php, and add a route:
Route::view('/posts', 'posts');Why reset the page on search?
Suppose you are on page 5 and type a search that returns only two pages. Without resetPage() Livewire asks for page 5 of the filtered results, which is empty. Resetting makes the first matching results appear immediately.
How do I change the pagination style?
Livewire uses Laravel's pagination views. To use simple Previous/Next links call simplePaginate(10) and render with links(). To edit the generated markup, publish Laravel's pagination views with php artisan vendor:publish --tag=laravel-pagination.
How do I check that it works?
- Open
/postsand confirm 10 posts and page links appear. - Click page 2 and verify that the page does not reload (the network tab shows a Livewire request).
- Type a word from a title and confirm the list filters and returns to page 1.
Common mistakes
- Forgetting the WithPagination trait: page links render but clicking them does nothing useful.
- Not resetting the page after a filter changes: users see empty pages.
- Querying inside the Blade template: it runs on every render. Use a computed property.
- Using a public property to hold the paginator: paginators should not be stored on the component. Return them from a computed property or
render(). - Missing wire:key in loops: lists can show stale rows after updates.
FAQ
Does this work with Livewire 3 too?
The ideas are the same (trait, paginator, resetPage). Livewire 3 uses class-based components in app/Livewire with a separate view, so the file layout differs.
Can I have two paginators on one component?
Yes. Give the second one a custom page name, for example paginate(10, pageName: 'commentsPage'), and use the matching name in resetPage('commentsPage').
Can I keep the page in the URL?
Livewire can sync the page number with the query string so a reload keeps the user on the same page. See the pagination page of the Livewire documentation for the current option.



