Chapter 4 of 9

Laravel 13 tutorial #4: the admin panel with Filament

verified on 7 September 2026 · 6 min

Quick answer

Laravel ships with no admin panel. This series now uses Filament: composer require filament/filament then php artisan filament:install --panels. Voyager, used in earlier versions, is marked abandoned on Packagist and no longer installs on Laravel 13.

By the end of this chapter, you will have a Filament admin panel installed on /admin, with an administrator account and an explicit access rule.

Laravel ships with no admin panel. You have to pick one, and that choice commits the project for years. This chapter explains why this series changed package, then installs Filament.

Why this chapter changed

Earlier versions of this tutorial used Voyager. That is no longer tenable.

tcg/voyager is marked as abandoned on Packagist. Its latest release, 1.8.0, dates from September 2024 and declares compatibility up to Laravel 11 only. On a Laravel 13 project, Composer simply refuses to install it: it first stops on the security advisories that cover every version, and its Laravel 11 constraint would make dependency resolution fail anyway.

That is the useful lesson of the episode: a tutorial resting on a package maintained by a single team becomes wrong the day that team stops. So the replacement has to be weighed on the health of the project as much as on its features.

What was looked at

Four candidates, measured on 2 September 2026 against the public Packagist data:

Package Last release Laravel 13 Downloads / month
tcg/voyager 1.8.0 (Sept 2024) no 23,614, but abandoned
ilsondev/voyager 2.2.1 (July 2026) yes 1
orchid/platform 14.53.0 (March 2026) yes 27,227
filament/filament 5.7.8 (Aug 2026) yes 3,324,015

ilsondev/voyager is a fork that works: it was installed on a fresh Laravel 13.30.1 for this revision, and the dashboard, the BREAD tool, the media library, the menu builder and the settings all responded correctly. But it totals 12 downloads and one star, and every one of its releases was published across three days in July 2026. It also pulls in intervention/image version 2, whose branch is no longer the current one. Recommending this fork to a beginner would be repeating the exact mistake that broke this tutorial.

That said, if you maintain an existing Voyager application and your first concern is moving to Laravel 13 without rewriting everything, this fork is an honest stepping stone. Treat it as a stage, not as a destination.

The choice: Filament

Filament wins on three counts.

  • It will not disappear tomorrow. 3.3 million downloads a month, a hundred times the audience of every alternative combined, and releases published every week.
  • The admin panel is code. Voyager stored the screen configuration in the database, clicked together in an interface. With Filament, each screen is a PHP class versioned along with the rest of the project. It can be reread, diffed and deployed like any other file.
  • It is a skill that pays off. Filament is now the default admin panel of the Laravel ecosystem. What you learn here will turn up again on a real project.

Two paid alternatives are worth knowing about, without being needed for this tutorial: Nova, the official solution from the Laravel team, and Backpack.

Installing Filament

The intl extension has to be there

Filament requires the PHP intl extension. Check for it before installing:

bash
php -m | grep intl

If the command returns nothing, the installation fails on this message, which never mentions Filament and leaves you puzzled:

code
filament/support[...] require ext-intl * -> it is missing from your system.
Install or enable PHP's intl extension.

The first chapter already advised checking it for that reason. On Debian or Ubuntu, sudo apt install php8.4-intl. On macOS with Homebrew, the extension is already there. On Windows with WampServer, untick then tick php_intl again in the PHP extensions menu. Restart your server afterwards.

The package and the panel

bash
composer require filament/filament
php artisan filament:install --panels

The second command creates app/Providers/Filament/AdminPanelProvider.php, publishes the panel’s CSS and JavaScript files into public, and clears the caches.

code
INFO  Successfully published assets!
INFO  Successfully upgraded!

Creating an account

bash
php artisan make:filament-user

The command asks for a name, an email address and a password, then confirms:

code
INFO  Success! admin@gekkode.test may now log in at http://localhost:8000/admin/login.

Head to http://127.0.0.1:8000/admin and log in. The dashboard is empty: that is expected, nothing has been handed to it yet. Chapter 6 will add posts, categories and tags to it.

Controlling who gets in

In a local environment, Filament lets any authenticated user in. Everywhere else, it turns everyone away with a 403 until the User model has said who is allowed in. It is a safe default, but it catches people out: the panel works on your machine and returns 403 the moment you go live.

The rule is declared by implementing the FilamentUser interface. Keep everything the shipped model already contains, the #[Fillable] and #[Hidden] attributes as well as the casts() method: only the interface and the canAccessPanel() method are added.

app/Models/User.php
<?php

namespace App\Models;

use Database\Factories\UserFactory;
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

#[Fillable(['name', 'email', 'password'])]
#[Hidden(['password', 'remember_token'])]
class User extends Authenticatable implements FilamentUser
{
    /** @use HasFactory<UserFactory> */
    use HasFactory, Notifiable;

    public function canAccessPanel(Panel $panel): bool
    {
        return str_ends_with($this->email, '@gekkode.test');
    }

    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }
}

Replace the domain with your own. This rule is deliberately simplistic: it is enough for a learning project, not for a public site. A real project relies on an is_admin column or on a role system.

The behaviour is checked by two tests. The second one matters as much as the first: in the testing environment, Filament refuses everyone as long as the rule does not exist, so a test that only checks the refusal would pass even without it.

tests/Feature/AdminTest.php
<?php

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class AdminTest extends TestCase
{
    use RefreshDatabase;

    public function test_un_email_hors_domaine_est_refuse(): void
    {
        $user = User::factory()->create(['email' => 'intrus@example.com']);

        $this->actingAs($user)->get('/admin')->assertForbidden();
    }

    public function test_un_email_du_domaine_est_accepte(): void
    {
        $user = User::factory()->create(['email' => 'admin@gekkode.test']);

        $this->actingAs($user)->get('/admin')->assertOk();
    }
}

Migrating from the Voyager version of this tutorial

If you followed this tutorial in its Voyager version, here is what changes.

  • The Voyager tables go away. The installation created ten of them: data_types, data_rows, menus, menu_items, settings, roles, permissions, permission_role, user_roles and translations. None of them are of any use now. Your business tables, posts, categories, tags, post_tag, users, are kept exactly as they are: Filament plugs into them with no data migration.
  • The BREAD configuration is lost, and that is deliberate. What you had clicked together in Voyager gets rewritten in PHP, once, in the files of chapter 6. That is half an hour of work, after which the configuration is under version control.
  • The setting() function is gone. Voyager provided it to read the site title and description. Replace setting('site.title') with config('app.name'), or with your own settings.
  • The posts slug workaround becomes pointless. Voyager shipped its own Post model, which forced you to rename the table slug to blog-posts and patch one of the package’s templates. Filament imposes no model, so the conflict disappears.
  • The admin address does not change. Filament installs on /admin as well.

To take Voyager out of an existing project: composer remove tcg/voyager, then drop its tables and the resources/views/vendor/voyager folder if it is still there. Back up the database first.

What this chapter did not cover

Fine-grained roles and permissions, theming the panel and dashboard widgets are beyond the scope of this series. The Filament documentation covers them.

The next chapter builds the home page of the public site.

Common errors

ext-intl is missing from your system Filament requires the intl extension. The Composer message never names Filament and leaves you puzzled. Install php-intl, then run the installation again.
The panel returns 403 outside the local environment Filament turns everyone away until the User model implements FilamentUser::canAccessPanel(). The panel works locally and breaks the moment you go live.
Composer refuses to install tcg/voyager The package stops at Laravel 11 and is marked abandoned. There is no Laravel 13 compatible release.
Newsletter

New tests, tutorials and projects, by e-mail.

Reproducible tests, versioned code, dated results. Never any spam.