Chaque outil interne finit par avoir besoin d'un panneau d'administration. Que vous lanciez un produit SaaS, gériez une boutique en ligne ou exploitiez un CRM interne, quelqu'un dans l'équipe aura besoin d'une interface propre pour gérer les utilisateurs, les commandes, le contenu et les paramètres. Dans l'écosystème Laravel en 2026, la réponse est sans ambiguïté — Filament v3 est désormais la solution dominante. Il combine la productivité de Livewire 3, l'élégance de Tailwind CSS v4 et un système de composants profondément réfléchi qui vous permet de construire un panneau d'administration soigné en quelques heures plutôt qu'en quelques semaines.
Dans ce tutoriel, nous allons construire NoqtaShop Admin, un panneau d'administration entièrement fonctionnel pour un backend e-commerce fictif. À la fin, vous aurez un tableau de bord opérationnel avec produits, catégories, commandes, contrôle d'accès basé sur les rôles, multi-tenant, widgets personnalisés et un build de production déployable.
Prérequis
Avant de commencer, assurez-vous d'avoir installé :
- PHP 8.3 ou plus récent
- Composer 2.7 ou plus récent
- Node.js 20 ou plus récent pour la compilation des assets
- Une connaissance pratique du routage Laravel, des modèles et des migrations
- Une familiarité avec Eloquent et Blade
- Un éditeur de code tel que VS Code ou PhpStorm
- Une base de données locale (PostgreSQL ou MySQL — nous utiliserons PostgreSQL)
Ce tutoriel suppose une expérience intermédiaire avec Laravel. Si vous êtes complètement nouveau sur Laravel, parcourez d'abord le Laravel Bootcamp officiel, puis revenez ici.
Ce que vous allez construire
NoqtaShop Admin inclura :
- Un écran de connexion soigné avec authentification par email et mot de passe
- Un tableau de bord avec widgets de revenus, commandes récentes et un graphique des ventes
- Des ressources pour les produits, catégories et commandes, chacune avec des pages CRUD complètes
- Des tableaux recherchables, filtrables et triables avec actions groupées
- Des composants de formulaire personnalisés pour les variantes de produits et descriptions enrichies
- Des permissions basées sur les rôles via spatie/laravel-permission
- Une frontière multi-tenant afin que chaque propriétaire de boutique ne voie que ses propres données
- Téléchargements de fichiers vers le stockage local avec aperçu d'image
- Un build prêt pour la production avec notifications en file d'attente et sauvegardes de base de données
Pourquoi Filament v3 en 2026
Il y a trois ans, construire un panneau d'administration Laravel signifiait combiner les licences Nova, les add-ons Backpack ou des tableaux de bord Vue faits maison. Chaque option avait de réels compromis. Nova était cher et lent à évoluer, Backpack nécessitait une configuration complexe, et les tableaux de bord DIY réinventaient la roue à chaque projet.
Filament v3 a changé l'équation. Il est open source, il est livré avec Livewire 3 et Alpine.js prêts à l'emploi, et sa bibliothèque de composants est la plus aboutie de l'écosystème PHP. La sortie v3 a ajouté la commutation de panneau, le multi-tenant, les thèmes personnalisés et un système d'actions qui vous permet de composer des boutons, modales et confirmations de manière déclarative. La communauté est devenue énorme, avec des centaines de plugins communautaires couvrant tout, des bibliothèques de médias à la conformité RGPD.
Comparé à Backpack, Filament vous offre un langage de design beaucoup plus cohérent et moins de surprises lors de la personnalisation. Comparé à Nova, il ne coûte rien et évolue à un rythme plus rapide. Comparé à un panneau React fait main, il est livré en jours plutôt qu'en mois.
Étape 1 : Configuration du projet
Commençons par créer un nouveau projet Laravel 11. Ouvrez votre terminal et exécutez l'installateur Laravel.
composer create-project laravel/laravel:^11.0 noqtashop
cd noqtashopConfigurez votre base de données dans le fichier .env. Nous recommandons PostgreSQL pour son support plus riche du JSON et de la recherche en texte intégral, mais MySQL fonctionne tout aussi bien.
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=noqtashop
DB_USERNAME=postgres
DB_PASSWORD=secret
APP_NAME="NoqtaShop Admin"
APP_URL=http://localhost:8000Créez la base de données, puis exécutez les migrations initiales pour vous assurer que tout se connecte correctement.
createdb noqtashop
php artisan migrateSi les migrations réussissent, vous êtes prêt à installer Filament.
Étape 2 : Installer Filament v3
Installez le package du panneau Filament avec Composer.
composer require filament/filament:"^3.3" -WMaintenant, exécutez l'installateur du panneau. Filament va échafauder un fournisseur de panneau, enregistrer les routes et publier un thème de démarrage.
php artisan filament:install --panelsLorsqu'il vous demandera un identifiant de panneau, entrez admin. L'installateur crée app/Providers/Filament/AdminPanelProvider.php, qui est le point d'entrée pour toute la configuration de l'administration.
Créez votre premier utilisateur administrateur. Cette commande crée une ligne dans la table users et accorde l'accès au panneau.
php artisan make:filament-userRemplissez les invites avec votre nom, votre email et un mot de passe robuste. Démarrez maintenant le serveur de développement et le serveur Vite dans deux terminaux distincts.
php artisan serve
npm install && npm run devVisitez http://localhost:8000/admin et connectez-vous. Vous verrez un tableau de bord Filament vide. L'habillage est déjà soigné — la barre latérale, la barre supérieure, l'inverseur de mode sombre et les fils d'Ariane fonctionnent tous d'emblée.
Étape 3 : Modéliser le domaine
Avant de générer des ressources Filament, nous avons besoin de données réelles. Créez les modèles de domaine pour notre boutique.
php artisan make:model Category -m
php artisan make:model Product -m
php artisan make:model Order -m
php artisan make:model OrderItem -mOuvrez database/migrations/xxxx_create_categories_table.php et définissez le schéma.
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->text('description')->nullable();
$table->boolean('is_visible')->default(true);
$table->unsignedBigInteger('parent_id')->nullable();
$table->foreign('parent_id')->references('id')->on('categories')->nullOnDelete();
$table->timestamps();
});Définissez ensuite la table des produits. Nous utilisons des colonnes JSON pour les variantes et une relation polymorphique pour les médias.
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->foreignId('category_id')->constrained()->cascadeOnDelete();
$table->string('name');
$table->string('slug')->unique();
$table->text('description')->nullable();
$table->decimal('price', 10, 2);
$table->integer('stock')->default(0);
$table->string('sku')->unique();
$table->boolean('is_visible')->default(true);
$table->boolean('is_featured')->default(false);
$table->json('variants')->nullable();
$table->string('image')->nullable();
$table->timestamps();
});Le schéma des commandes est simple — une ligne d'en-tête plus une table de lignes d'articles.
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->string('number')->unique();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('status')->default('pending');
$table->decimal('total', 10, 2);
$table->string('currency', 3)->default('USD');
$table->text('notes')->nullable();
$table->timestamps();
});
Schema::create('order_items', function (Blueprint $table) {
$table->id();
$table->foreignId('order_id')->constrained()->cascadeOnDelete();
$table->foreignId('product_id')->constrained();
$table->integer('quantity');
$table->decimal('unit_price', 10, 2);
$table->timestamps();
});Ajoutez les relations aux modèles. Ouvrez app/Models/Product.php et définissez les liens.
class Product extends Model
{
protected $fillable = [
'category_id', 'name', 'slug', 'description',
'price', 'stock', 'sku', 'is_visible', 'is_featured',
'variants', 'image',
];
protected $casts = [
'price' => 'decimal:2',
'is_visible' => 'boolean',
'is_featured' => 'boolean',
'variants' => 'array',
];
public function category()
{
return $this->belongsTo(Category::class);
}
}Exécutez les migrations et insérez quelques catégories pour avoir des données de travail.
php artisan migrate
php artisan tinkerDans Tinker, créez trois catégories d'exemple.
\App\Models\Category::create(['name' => 'Apparel', 'slug' => 'apparel']);
\App\Models\Category::create(['name' => 'Electronics', 'slug' => 'electronics']);
\App\Models\Category::create(['name' => 'Books', 'slug' => 'books']);
exitÉtape 4 : Générer votre première ressource
Les ressources Filament sont des classes PHP qui décrivent comment un modèle doit être affiché dans le panneau. Générez-en une pour le modèle Category.
php artisan make:filament-resource Category --generateLe drapeau --generate indique à Filament d'inspecter les colonnes de la base de données et de produire un schéma par défaut sensé. Rafraîchissez le panneau d'administration — vous verrez un nouveau lien Categories dans la barre latérale.
Cliquez sur Categories. La vue liste affiche un tableau avec des colonnes pour le nom et le slug, et un bouton Create Category vous emmène à un formulaire. Le CRUD est fonctionnel sans une seule ligne de code écrite à la main.
Personnalisons-le maintenant. Ouvrez app/Filament/Resources/CategoryResource.php et affinez le formulaire.
public static function form(Form $form): Form
{
return $form->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255)
->live(onBlur: true)
->afterStateUpdated(fn (string $state, callable $set) =>
$set('slug', Str::slug($state))
),
Forms\Components\TextInput::make('slug')
->required()
->unique(ignoreRecord: true),
Forms\Components\Textarea::make('description')
->rows(4),
Forms\Components\Toggle::make('is_visible')
->label('Visible to customers')
->default(true),
Forms\Components\Select::make('parent_id')
->label('Parent category')
->relationship('parent', 'name')
->searchable(),
]);
}Le modificateur live(onBlur: true) indique à Filament d'écouter les événements blur et de remplir automatiquement le slug lorsqu'un nom est saisi. Ce genre de petite touche est ce qui rend Filament tellement abouti.
Étape 5 : Construire une ressource produit soignée
Exécutez aussi le générateur pour les produits.
php artisan make:filament-resource Product --generateOuvrez ProductResource.php et remplacez le schéma de formulaire par une version plus riche utilisant des sections et des colonnes.
public static function form(Form $form): Form
{
return $form->schema([
Forms\Components\Section::make('Basic information')
->columns(2)
->schema([
Forms\Components\TextInput::make('name')
->required()
->live(onBlur: true)
->afterStateUpdated(fn ($state, callable $set) =>
$set('slug', Str::slug($state))
),
Forms\Components\TextInput::make('slug')
->required()
->unique(ignoreRecord: true),
Forms\Components\Select::make('category_id')
->relationship('category', 'name')
->required()
->searchable()
->preload(),
Forms\Components\TextInput::make('sku')
->required()
->unique(ignoreRecord: true),
]),
Forms\Components\Section::make('Pricing and stock')
->columns(3)
->schema([
Forms\Components\TextInput::make('price')
->numeric()
->prefix('$')
->required(),
Forms\Components\TextInput::make('stock')
->numeric()
->default(0),
Forms\Components\Toggle::make('is_visible')
->default(true),
]),
Forms\Components\Section::make('Description')
->schema([
Forms\Components\RichEditor::make('description')
->columnSpanFull(),
]),
Forms\Components\Section::make('Image')
->schema([
Forms\Components\FileUpload::make('image')
->image()
->imageEditor()
->directory('products')
->columnSpanFull(),
]),
]);
}Le formulaire ressemble maintenant à un véritable éditeur de produit — les sections regroupent les champs liés, un éditeur de texte enrichi gère la description et le composant d'upload de fichiers prend en charge le recadrage et le redimensionnement dans le navigateur.
Ensuite, personnalisez le tableau.
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\ImageColumn::make('image')
->circular(),
Tables\Columns\TextColumn::make('name')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('category.name')
->label('Category')
->sortable(),
Tables\Columns\TextColumn::make('price')
->money('USD')
->sortable(),
Tables\Columns\TextColumn::make('stock')
->sortable()
->badge()
->color(fn (int $state): string => match (true) {
$state === 0 => 'danger',
$state < 10 => 'warning',
default => 'success',
}),
Tables\Columns\IconColumn::make('is_visible')
->boolean(),
])
->filters([
Tables\Filters\SelectFilter::make('category')
->relationship('category', 'name'),
Tables\Filters\TernaryFilter::make('is_visible'),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}Le modificateur badge() sur la colonne stock avec des couleurs conditionnelles est un excellent exemple de la manière dont Filament garde une API ergonomique. En une seule expression, le stock faible devient jaune et l'épuisement rouge.
Étape 6 : Ajouter la ressource Order et un Relation Manager
Les commandes sont plus complexes parce qu'elles ont des éléments enfants. Générez d'abord la ressource.
php artisan make:filament-resource Order --generateMaintenant, générez un relation manager pour les éléments de commande. Les relation managers sont des tableaux imbriqués qui apparaissent sur la page de détail de la ressource parente.
php artisan make:filament-relation-manager OrderResource items product_idCela échafaude OrderResource/RelationManagers/ItemsRelationManager.php. Configurez-le pour afficher les éléments de commande en ligne.
public function table(Table $table): Table
{
return $table
->recordTitleAttribute('product.name')
->columns([
Tables\Columns\TextColumn::make('product.name'),
Tables\Columns\TextColumn::make('quantity'),
Tables\Columns\TextColumn::make('unit_price')->money('USD'),
])
->headerActions([
Tables\Actions\CreateAction::make()
->form([
Forms\Components\Select::make('product_id')
->relationship('product', 'name')
->required(),
Forms\Components\TextInput::make('quantity')
->numeric()
->required(),
Forms\Components\TextInput::make('unit_price')
->numeric()
->required(),
]),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
]);
}Enregistrez le relation manager sur la ressource parente en l'ajoutant à la méthode getRelations de OrderResource.
public static function getRelations(): array
{
return [
RelationManagers\ItemsRelationManager::class,
];
}Maintenant, lorsque vous ouvrez une commande, vous verrez un tableau intégré pour les lignes d'articles. Vous pouvez ajouter, modifier et supprimer des lignes sans quitter la page.
Étape 7 : Widgets de tableau de bord personnalisés
Le tableau de bord Filament par défaut est vide. Ajoutons trois widgets — une vue d'ensemble des statistiques, un graphique et un tableau des commandes récentes.
php artisan make:filament-widget RevenueOverview --stats-overviewOuvrez app/Filament/Widgets/RevenueOverview.php et remplacez son corps par de vrais chiffres.
class RevenueOverview extends BaseWidget
{
protected function getStats(): array
{
return [
Stat::make('Total revenue', '$' . number_format(Order::sum('total'), 2))
->description('All time')
->descriptionIcon('heroicon-m-arrow-trending-up')
->color('success'),
Stat::make('Orders this month', Order::whereMonth('created_at', now()->month)->count())
->description('Created in current month')
->color('primary'),
Stat::make('Active products', Product::where('is_visible', true)->count())
->color('warning'),
];
}
}Générez un widget graphique pour les ventes dans le temps.
php artisan make:filament-widget SalesChart --chartDans SalesChart.php, retournez un jeu de données qui agrège les commandes par jour pour les 30 derniers jours.
protected function getData(): array
{
$data = Trend::model(Order::class)
->between(start: now()->subDays(30), end: now())
->perDay()
->sum('total');
return [
'datasets' => [
[
'label' => 'Daily revenue',
'data' => $data->map(fn ($value) => $value->aggregate),
'fill' => true,
],
],
'labels' => $data->map(fn ($value) => $value->date),
];
}
protected function getType(): string
{
return 'line';
}L'aide Trend provient de flowframe/laravel-trend, un petit package qui transforme les requêtes de séries temporelles en une seule ligne. Installez-le maintenant.
composer require flowframe/laravel-trendRechargez le tableau de bord. Vous verrez de vraies statistiques de revenus et un graphique sparkline. Ajoutez les widgets au tableau de bord en modifiant AdminPanelProvider.
->widgets([
Widgets\RevenueOverview::class,
Widgets\SalesChart::class,
])Étape 8 : Contrôle d'accès basé sur les rôles
Un véritable panneau d'administration a besoin de rôles et de permissions. Installez le package spatie, qui s'intègre parfaitement à Filament via un plugin communautaire.
composer require spatie/laravel-permission
composer require bezhansalleh/filament-shield
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate
php artisan shield:install adminL'installateur Shield crée une ressource Roles, génère les permissions par défaut et met à jour votre modèle User pour utiliser le trait HasRoles. Exécutez le générateur de permissions pour créer des permissions par ressource.
php artisan shield:generate --allOuvrez le panneau et vous verrez une nouvelle section Roles. Créez un rôle Manager, attribuez les permissions de visualisation et de modification pour les produits et commandes, puis créez un deuxième utilisateur administrateur affecté à ce rôle. Connectez-vous en tant que cet utilisateur et vérifiez que la barre latérale n'affiche que les ressources auxquelles il a accès.
Pour la production, protégez le panneau lui-même avec l'interface canAccessPanel de Filament sur le modèle User.
class User extends Authenticatable implements FilamentUser
{
public function canAccessPanel(Panel $panel): bool
{
return $this->hasAnyRole(['super_admin', 'manager']);
}
}Étape 9 : Multi-tenant
Filament v3 a introduit un multi-tenant de premier ordre. Chaque tenant devient un scope dans l'URL et une contrainte sur chaque requête. Nous modéliserons les tenants comme des entités Shop.
php artisan make:model Shop -mDéfinissez la migration et le modèle Shop avec une relation aux utilisateurs.
Schema::create('shops', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->timestamps();
});
Schema::create('shop_user', function (Blueprint $table) {
$table->foreignId('shop_id')->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->primary(['shop_id', 'user_id']);
});Ajoutez une colonne shop_id aux produits et commandes, puis activez le multi-tenant dans AdminPanelProvider.
return $panel
->id('admin')
->path('admin')
->tenant(Shop::class, slugAttribute: 'slug')
->tenantRegistration(RegisterShop::class);Implémentez l'interface HasTenants sur le modèle User afin que Filament sache à quelles boutiques un utilisateur appartient.
class User extends Authenticatable implements FilamentUser, HasTenants
{
public function getTenants(Panel $panel): Collection
{
return $this->shops;
}
public function canAccessTenant(Model $tenant): bool
{
return $this->shops()->whereKey($tenant)->exists();
}
}Filament affichera désormais un sélecteur de tenant dans la barre supérieure. Les URLs deviennent /admin/{shopSlug}/products, et les requêtes sont automatiquement contraintes au tenant actif.
Étape 10 : Stockage de fichiers et sauvegardes
Configurez le stockage local pour les images de produits. Dans config/filesystems.php, assurez-vous que le disque public est lié symboliquement.
php artisan storage:linkPour la production, basculez le disque vers S3 ou un fournisseur compatible tel que Cloudflare R2. Ajoutez les identifiants à .env et changez le composant FileUpload pour utiliser le disque configuré.
Forms\Components\FileUpload::make('image')
->image()
->disk('s3')
->visibility('public')
->directory('products')Installez le package Spatie de sauvegarde pour pouvoir dormir tranquille la nuit.
composer require spatie/laravel-backup
php artisan vendor:publish --provider="Spatie\Backup\BackupServiceProvider"Planifiez une sauvegarde nocturne dans app/Console/Kernel.php.
$schedule->command('backup:run')->dailyAt('02:00');
$schedule->command('backup:clean')->dailyAt('02:30');Étape 11 : Déploiement en production
Pour la production, nous recommandons Laravel Forge ou un déploiement basé sur Docker via Coolify. Dans les deux cas, le workflow est identique. Tirez le code, installez les dépendances Composer et npm, compilez les assets et exécutez les migrations.
Un script de déploiement typique ressemble à ceci.
git pull origin main
composer install --no-dev --optimize-autoloader
npm ci && npm run build
php artisan migrate --force
php artisan filament:cache-components
php artisan icons:cache
php artisan optimize
php artisan queue:restartLa commande filament:cache-components pré-compile les vues Blade pour les composants Filament, réduisant la latence de la première requête de plusieurs centaines de millisecondes. Exécutez optimize pour mettre en cache la configuration et les routes.
Configurez un worker de file d'attente via Supervisor ou systemd, puisque Filament utilise des notifications en file et que les actions groupées peuvent être différées.
[program:noqtashop-worker]
command=php /srv/noqtashop/artisan queue:work --tries=3 --timeout=90
autostart=true
autorestart=true
user=deploy
numprocs=2Tester votre implémentation
Filament s'intègre aux helpers de test HTTP de Laravel. Écrivez un test de fumée rapide pour s'assurer que le tableau de bord se charge.
test('admin dashboard loads for authenticated users', function () {
$user = User::factory()->create();
$user->assignRole('super_admin');
$this->actingAs($user)
->get('/admin')
->assertOk();
});Pour chaque ressource, écrivez un test Pest qui crée un enregistrement et vérifie qu'il apparaît dans le tableau.
test('manager can create a product', function () {
$manager = User::factory()->create();
$manager->assignRole('manager');
$this->actingAs($manager);
Livewire::test(CreateProduct::class)
->fillForm([
'name' => 'Demo Hoodie',
'slug' => 'demo-hoodie',
'sku' => 'DEMO-1',
'price' => 49.99,
'category_id' => Category::factory()->create()->id,
])
->call('create')
->assertHasNoFormErrors();
$this->assertDatabaseHas('products', ['sku' => 'DEMO-1']);
});Dépannage
Quelques problèmes reviennent souvent lors du déploiement de panneaux Filament.
Première requête lente après le déploiement. Exécutez filament:cache-components et view:cache dans le script de déploiement.
Échec silencieux des téléchargements de fichiers. Vérifiez que php.ini a upload_max_filesize et post_max_size réglés suffisamment haut, et que la politique de votre bucket S3 autorise les lectures publiques.
Sélecteur de tenant manquant. Vérifiez que le modèle User implémente HasTenants et que getTenants retourne une collection non vide.
Permissions incohérentes. Exécutez php artisan shield:generate --all après chaque nouvelle ressource pour que les politiques restent synchronisées.
Étapes suivantes
Vous disposez maintenant d'un panneau d'administration prêt pour la production. À partir d'ici, vous pouvez l'étendre dans de nombreuses directions.
- Ajoutez le package Filament Notifications pour les toasts en temps réel et un menu cloche
- Intégrez un plugin de bibliothèque de médias tel que
awcodes/filament-curatorpour une gestion centralisée des assets - Construisez des pages personnalisées pour les KPIs qui ne se mappent pas proprement à une ressource unique
- Câblez une vitrine Livewire publique qui consomme les mêmes modèles
- Migrez le stockage de fichiers vers Cloudflare R2 pour une livraison de médias bon marché et rapide
Si vous venez de Next.js, vous apprécierez tout ce que Filament vous offre gratuitement. Si vous étendez une application Laravel existante, Filament s'intègre à côté de vos routes sans rien réécrire.
Conclusion
En une journée, nous avons construit un panneau d'administration complet avec ressources, tableaux de bord, permissions et multi-tenant. Filament v3 a atteint un niveau de finition tel qu'il n'est plus un choix de niche — c'est la réponse par défaut pour le travail d'administration Laravel en 2026. La combinaison de Livewire 3, d'une API de composants réfléchie et d'une communauté passionnée signifie que vous passez votre temps sur de vraies fonctionnalités produit au lieu de peaufiner l'habillage autour.
Utilisez ce tutoriel comme fondation, déployez votre propre version et explorez l'écosystème de plugins Filament. La page des plugins communautaires liste des centaines d'extensions open source couvrant tout, de la facturation Stripe aux intégrations OAuth. Choisissez ceux dont vous avez besoin, expédiez vite et laissez votre panneau d'administration suivre le rythme de votre produit.