<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: yebor974</title>
    <description>The latest articles on DEV Community by yebor974 (@yebor974).</description>
    <link>https://dev.to/yebor974</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3124779%2Ff412870f-3243-4d38-9328-0b05c87f20ae.jpeg</url>
      <title>DEV Community: yebor974</title>
      <link>https://dev.to/yebor974</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yebor974"/>
    <language>en</language>
    <item>
      <title>Laravel Filament Multi-Tenant Starter - Build your app fast</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Fri, 26 Jun 2026 13:52:00 +0000</pubDate>
      <link>https://dev.to/filamentmastery/laravel-filament-multi-tenant-starter-build-your-app-fast-384m</link>
      <guid>https://dev.to/filamentmastery/laravel-filament-multi-tenant-starter-build-your-app-fast-384m</guid>
      <description>&lt;p&gt;Building a SaaS with Laravel Filament? You need more than two panels. You need teams, invitations, tenant-aware permissions, and a member experience that works for multiple users within the same organization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Laravel Filament Multi-Tenant Starter&lt;/strong&gt;  does it for you. Backend panel, tenant member panel, team-based multi-tenancy, invitation system, team roles. All wired up and ready to go with Laravel 13 and Filament PHP v5.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Two panels, two worlds
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend panel&lt;/strong&gt; for your admins, accessible via subdomain or path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Member panel&lt;/strong&gt; for your users, tenant-aware, with team switching built in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each panel has its own middleware, routing config, and permission scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  Team-based multi-tenancy
&lt;/h3&gt;

&lt;p&gt;Multi-tenancy is powered by a &lt;code&gt;Team&lt;/code&gt; model. Each user can belong to multiple teams and switch between them directly from the member panel.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users can belong to multiple teams simultaneously&lt;/li&gt;
&lt;li&gt;Team switching built into the member panel&lt;/li&gt;
&lt;li&gt;Each team has its own profile page (name), editable by Team Admins&lt;/li&gt;
&lt;li&gt;Tenant-aware resources and access control throughout the member panel&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Two-tier role system
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Application roles&lt;/strong&gt; secure the Filament panels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Backend&lt;/code&gt; - access to the backend admin panel&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Member&lt;/code&gt; - access to the member panel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Team roles&lt;/strong&gt; govern what users can do inside their team:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Team_Admin&lt;/code&gt; - manages team members, sends invitations, edits team profile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All roles are pre-seeded and defined via enums, clean, extensible, no magic strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Complete invitation flow
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;For existing users:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Team Admin sends an invitation&lt;/li&gt;
&lt;li&gt;User accepts&lt;/li&gt;
&lt;li&gt;User is immediately attached to the team&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;For new users:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Team Admin sends an invitation&lt;/li&gt;
&lt;li&gt;User receives an invitation email&lt;/li&gt;
&lt;li&gt;User registers&lt;/li&gt;
&lt;li&gt;User is automatically attached to the team and can access the panel immediately&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Secure authentication
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Email invitation system for backend and member users&lt;/li&gt;
&lt;li&gt;First-login password setup enforced&lt;/li&gt;
&lt;li&gt;Password renewal flow via &lt;a href="https://github.com/yebor974/filament-renew-password?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Filament Renew Password&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;MFA with authenticator app support&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Production infrastructure, included
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://laravel.com/docs/horizon?ref=filamentmastery.com" rel="noopener noreferrer"&gt;&lt;strong&gt;Laravel Horizon&lt;/strong&gt;&lt;/a&gt; - queue monitoring, backend-only access&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/opcodesio/log-viewer?ref=filamentmastery.com" rel="noopener noreferrer"&gt;&lt;strong&gt;Logs Viewer&lt;/strong&gt;&lt;/a&gt; - backend-only access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt;  for queues and cache&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-stage Docker setup&lt;/strong&gt;  - PHP-FPM, Nginx, PostgreSQL, Redis, Horizon, scheduler, all wired with healthchecks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Developer experience
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app/Filament/Shared&lt;/code&gt; - shared schemas between panels&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/Models/TeamUser&lt;/code&gt; - team membership with role scoping&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app/Services/TeamUserService&lt;/code&gt; - team user logic cleanly extracted&lt;/li&gt;
&lt;li&gt;Per-user timezone applied automatically everywhere&lt;/li&gt;
&lt;li&gt;Strong password policy enforced globally&lt;/li&gt;
&lt;li&gt;Light &amp;amp; Dark theme switch + empty custom theme scaffold&lt;/li&gt;
&lt;li&gt;Database notifications (Filament native)&lt;/li&gt;
&lt;li&gt;Fully localized - English by default&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/barryvdh/laravel-debugbar?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Laravel Debugbar&lt;/a&gt;, &lt;a href="https://phpstan.org/?ref=filamentmastery.com" rel="noopener noreferrer"&gt;PHPStan&lt;/a&gt;, &lt;a href="https://laravel.com/docs/pint?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Laravel Pint&lt;/a&gt;, &lt;a href="https://pestphp.com/?ref=filamentmastery.com" rel="noopener noreferrer"&gt;PestPHP&lt;/a&gt; included&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;Laravel 13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Admin panel&lt;/td&gt;
&lt;td&gt;Filament PHP v5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;TALL Stack: Tailwind v4, Alpine, Livewire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;td&gt;Pest PHP v4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PHP&lt;/td&gt;
&lt;td&gt;8.3+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;PostgreSQL or MySQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Queue &amp;amp; cache&lt;/td&gt;
&lt;td&gt;Redis + Laravel Horizon&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Who is it for?
&lt;/h2&gt;

&lt;p&gt;✅ SaaS applications with team-based access&lt;br&gt;&lt;br&gt;
✅ B2B platforms where each client is a team&lt;br&gt;&lt;br&gt;
✅ Project management tools&lt;br&gt;&lt;br&gt;
✅ Any app where users belong to organizations with different roles&lt;br&gt;&lt;br&gt;
✅ Add your own billing layer (Laravel Cashier, Paddle, LemonSqueezy)&lt;/p&gt;

&lt;p&gt;Need a simpler setup without multi-tenancy? See the &lt;a href="https://filamentmastery.com/articles/laravel-filament-multipanel-starter-build-your-app-fast/" rel="noopener noreferrer"&gt;Multipanel Starter&lt;/a&gt; or the &lt;a href="https://filamentmastery.com/articles/laravel-filament-backend-starter-build-your-admin-panel-fast/" rel="noopener noreferrer"&gt;Backend Starter&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Compare starter templates
&lt;/h2&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Backend Starter&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Multipanel Starter&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Multi-Tenant Starter&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Laravel 13 + Filament v5&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend Panel&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Member Panel&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subdomain or Path Routing&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ &lt;em&gt;(per panel)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;✅ &lt;em&gt;(per panel)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email Invitation&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public Registration&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First-login Password Renewal&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MFA (App authenticator)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Roles &amp;amp; Permissions (Spatie)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team-based Multi-Tenancy&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team Switching&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team Invitations&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team Profile Management&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team Roles (Admin)&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-panel Permission Scope&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-user Timezone&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logs Viewer&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laravel Horizon&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dark Mode&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-language&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Setup&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use case&lt;/td&gt;
&lt;td&gt;Internal tools&lt;/td&gt;
&lt;td&gt;Full-stack app&lt;/td&gt;
&lt;td&gt;SaaS, B2B platforms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Billing
&lt;/h2&gt;

&lt;p&gt;This starter does not include a billing module. The Team model is designed to be extended. Add your Stripe customer ID, subscription status, or any billing-related fields directly to the Team model and TeamUser as needed.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to install
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Without Docker
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env
php artisan key:generate
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run build

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Configure your &lt;code&gt;.env&lt;/code&gt;, database, Redis, mail, and your panel access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# Backend panel at backend.yourdomain.com
&lt;/span&gt;&lt;span class="py"&gt;FILAMENT_PANELS_BACKEND_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;backend.yourdomain.com&lt;/span&gt;
&lt;span class="py"&gt;FILAMENT_PANELS_BACKEND_PATH&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;

&lt;span class="c"&gt;# Or at yourdomain.com/backend
&lt;/span&gt;&lt;span class="py"&gt;FILAMENT_PANELS_BACKEND_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="py"&gt;FILAMENT_PANELS_BACKEND_PATH&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;backend&lt;/span&gt;

&lt;span class="c"&gt;# Member panel at member.yourdomain.com
&lt;/span&gt;&lt;span class="py"&gt;FILAMENT_PANELS_MEMBER_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;member.yourdomain.com&lt;/span&gt;
&lt;span class="py"&gt;FILAMENT_PANELS_MEMBER_PATH&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;

&lt;span class="c"&gt;# Or at yourdomain.com/member
&lt;/span&gt;&lt;span class="py"&gt;FILAMENT_PANELS_MEMBER_DOMAIN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="py"&gt;FILAMENT_PANELS_MEMBER_PATH&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;member&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the setup command - migrations, roles &amp;amp; permissions, first backend user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan backend:setup

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;All panels are live.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  With Docker (recommended)
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;docker-compose.example.yaml&lt;/code&gt; is included with everything pre-configured: PHP-FPM, Nginx, PostgreSQL, Redis, Horizon, and the scheduler. A dedicated &lt;code&gt;bootstrap&lt;/code&gt; service runs migrations automatically before the app starts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For a deep dive into the Docker architecture:&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://filamentmastery.com/articles/production-ready-docker-setup-for-laravel-filament/" rel="noopener noreferrer"&gt;Production-Ready Docker Setup for Laravel Filament&lt;/a&gt;&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://filamentmastery.com/articles/production-ready-docker-compose-for-laravel-filament/" rel="noopener noreferrer"&gt;Production-Ready Docker Compose for Laravel Filament&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;docker-compose.example.yaml docker-compose.yaml
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.docker.example .env
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;php-fpm php artisan backend:setup

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Access your panels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend: &lt;code&gt;https://localhost:4443/backend&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Member: &lt;code&gt;https://localhost:4443/member&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Always use &lt;code&gt;https://&lt;/code&gt; - Nginx expects SSL on port 4443. ⚠️ Self-signed certificate warning in local dev is expected. Certificates are provided in &lt;code&gt;docker/example/nginx/certs/&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For production, adapt the compose file and Nginx config to your own infrastructure. See the &lt;a href="https://filamentmastery.com/articles/production-ready-gitlab-ci-pipeline-for-laravel-filament/" rel="noopener noreferrer"&gt;GitLab CI guide&lt;/a&gt; for a complete production pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visual overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fua1s3xr3wjnlux6o9lcs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fua1s3xr3wjnlux6o9lcs.png" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Member Login&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gbpfqcpg2ox43t9ndie.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gbpfqcpg2ox43t9ndie.png" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Member Sign up&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18kuj31fjxtul6yn5vkp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18kuj31fjxtul6yn5vkp.png" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Member Team Registration&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3r38znal6foehz6980qx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3r38znal6foehz6980qx.png" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Member Dashboard&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnt34ju36ib57pfhzwwk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnt34ju36ib57pfhzwwk.png" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Member Team Profile&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0d1mn5gvdorhkygy5y5g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0d1mn5gvdorhkygy5y5g.png" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Member Team Users List&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8go1otazwjw0cn0mv0p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8go1otazwjw0cn0mv0p.png" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Member Team User Invite&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvzpk6k6dfsu5gkkn9bmq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvzpk6k6dfsu5gkkn9bmq.webp" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Member Team User Invitation Email&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2nrxi1pxs04znl0nz8on.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2nrxi1pxs04znl0nz8on.webp" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="800" height="434"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Member Team User Invitation On Login Page&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo7l3h4zqw4p1fcygw3h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo7l3h4zqw4p1fcygw3h.png" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="799" height="440"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Backend Dashboard&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjk8j8dogv66rpzm3eej2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjk8j8dogv66rpzm3eej2.png" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="799" height="440"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Backend Members List&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiu7cixyckrztsxzss4ja.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiu7cixyckrztsxzss4ja.png" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="799" height="440"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Backend Teams List&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flkek9ez6vd8cmdplky94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flkek9ez6vd8cmdplky94.png" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="799" height="440"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Backend Teams Edit&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11cg02ivk42cc0wk50g2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11cg02ivk42cc0wk50g2.png" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Backend Teams List - Attach User&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxwj7w1hd6z8vkwvwhh7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxwj7w1hd6z8vkwvwhh7.png" alt="Laravel Filament Multi-Tenant Starter - Build your app fast" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Laravel Filament Multi Tenant Backend Backend Users List&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Get access
&lt;/h2&gt;

&lt;p&gt;The Multi-Tenant Starter is included in the  &lt;strong&gt;Member tier&lt;/strong&gt; of Filament Mastery, along with the Backend Starter, the Multipanel Starter, all premium articles, and every future starter and guide.&lt;/p&gt;

&lt;p&gt;Your membership grants you the right to use this starter on unlimited personal or client projects&lt;/p&gt;

&lt;p&gt;&lt;a href="https://filamentmastery.com/" rel="noopener noreferrer"&gt;Join Filament Mastery -&amp;gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>filament</category>
      <category>starter</category>
      <category>laravel</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Understanding Filament themes in v4/v5: from Colors to custom CSS</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Fri, 26 Jun 2026 07:41:24 +0000</pubDate>
      <link>https://dev.to/filamentmastery/understanding-filament-themes-in-v4v5-from-colors-to-custom-css-57a5</link>
      <guid>https://dev.to/filamentmastery/understanding-filament-themes-in-v4v5-from-colors-to-custom-css-57a5</guid>
      <description>&lt;p&gt;Filament provides a flexible theming system that lets you customize the look and feel of your admin panel. This guide covers how to create and apply custom themes for Filament v4/v5 with Tailwind CSS v4.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is an updated version of an earlier guide written for &lt;a href="https://filamentmastery.com/articles/customize-your-filament-panel-theme/" rel="noopener noreferrer"&gt;Filament v3 and Tailwind v3&lt;/a&gt;. The overall approach is similar, but what the theme command generates and the CSS syntax have changed with Tailwind v4's CSS-first configuration. If you're still on Filament v3, the original guide is available here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Setting primary colors for panels
&lt;/h2&gt;

&lt;p&gt;Before touching the theme files, the simplest customization is setting your panel's color palette. This part hasn't changed: Filament still uses the &lt;code&gt;Filament\Support\Colors\Color&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: define colors per panel&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your panel provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Filament\Support\Colors\Color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;panel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Panel&lt;/span&gt; &lt;span class="nv"&gt;$panel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Panel&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$panel&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'danger'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Rose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'gray'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'info'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'primary'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Indigo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'success'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Emerald&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'warning'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Orange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 2: define colors globally&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;AppServiceProvider&lt;/code&gt;, applied across all panels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Filament\Facades\Filament&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Filament\Support\Colors\Color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Filament&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;defaultColors&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'danger'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Rose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'gray'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'info'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'primary'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Indigo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'success'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Emerald&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'warning'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Orange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When to stop here:&lt;/strong&gt;  if your goal is only to change branding colors, &lt;code&gt;-&amp;gt;colors()&lt;/code&gt; is all you need. Create a custom theme only when you need structural UI changes, like repositioning elements, changing spacing, or styling parts of the interface that color tokens don't reach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating the theme
&lt;/h2&gt;

&lt;p&gt;The command itself hasn't changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:filament-theme

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll be prompted for a panel name (default: &lt;code&gt;admin&lt;/code&gt;). What changed is what this command generates and does for you.&lt;/p&gt;

&lt;p&gt;On a Tailwind v4 setup, running it for a &lt;code&gt;member&lt;/code&gt; panel generates a theme.css scoped to that panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'../../../../vendor/filament/filament/resources/css/theme.css'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../app/Filament/Member/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../resources/views/filament/member/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;@source&lt;/code&gt; paths point specifically to &lt;code&gt;Member&lt;/code&gt;, not a generic  &lt;code&gt;Filament/**/*&lt;/code&gt;. Each panel gets its own scoped theme out of the box.&lt;/p&gt;

&lt;p&gt;That's it: the generated file is intentionally minimal. If you want a dedicated place for CSS variables or light/dark overrides, you can add your own &lt;code&gt;@layer base&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@layer&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;:root&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;class&lt;/span&gt;&lt;span class="o"&gt;~=&lt;/span&gt;&lt;span class="s1"&gt;"dark"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other change worth noting: in Filament v3, you had to manually add the theme to &lt;code&gt;vite.config.js&lt;/code&gt; and register it with &lt;code&gt;-&amp;gt;viteTheme()&lt;/code&gt; in your panel provider. In v4/v5, the command does both for you automatically. No manual wiring required.&lt;/p&gt;

&lt;p&gt;A few things changed compared to Tailwind v3:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No more &lt;code&gt;tailwind.config.js&lt;/code&gt;.&lt;/strong&gt;  Tailwind v4 no longer requires a &lt;strong&gt;&lt;code&gt;tailwind.config.js&lt;/code&gt;&lt;/strong&gt; file for basic theme customization. It moved to CSS-first configuration. There's nothing left to generate or maintain in a separate JS config file for the theme.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;@source&lt;/code&gt; directives replace the old &lt;code&gt;content&lt;/code&gt; array.&lt;/strong&gt;  Tailwind v4 scans these paths directly from CSS to know which files to look at for class usage, instead of declaring them in a JS config.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The generated file is intentionally minimal.&lt;/strong&gt;  Just the import and the &lt;code&gt;@source&lt;/code&gt; paths for that panel. No boilerplate &lt;code&gt;@layer base&lt;/code&gt; block is added automatically; that's something you add yourself if and when you need it, as shown above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding your own directories to &lt;a class="mentioned-user" href="https://dev.to/source"&gt;@source&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The generated &lt;code&gt;@source&lt;/code&gt; directives only cover Filament's own files. Your resources, components, or anything outside &lt;code&gt;app/Filament&lt;/code&gt; won't be scanned by default. If you use Tailwind classes in Blade components, Livewire components, or custom views, you need to add those directories yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../app/Filament/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../resources/views/filament/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../resources/views/components/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../resources/views/livewire/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../app/Livewire/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forget to add a directory here, and Tailwind will purge classes it never saw being used, even if they're correctly written in your code. Rebuild with &lt;code&gt;npm run build&lt;/code&gt; after adding new paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gets registered automatically
&lt;/h2&gt;

&lt;p&gt;Since the command handles registration for you, &lt;code&gt;vite.config.js&lt;/code&gt; already includes the new theme after running &lt;code&gt;make:filament-theme&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;laravel&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;laravel-vite-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tailwindcss&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@tailwindcss/vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;laravel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resources/css/app.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resources/css/filament/admin/theme.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resources/js/app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="na"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="nf"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And your panel provider already has the theme registered too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;viteTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'resources/css/filament/admin/theme.css'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing left to wire up manually. Just run the command, and both files are updated for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multiple panels, multiple themes
&lt;/h2&gt;

&lt;p&gt;If your application has more than one panel, an admin panel and a member panel for example, each one gets its own theme file and is registered independently.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;make:filament-theme&lt;/code&gt; once per panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:filament-theme backend
php artisan make:filament-theme member

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each run automatically adds its theme to &lt;code&gt;vite.config.js&lt;/code&gt; and registers it in the matching panel provider. You'll end up with both entries already in place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;laravel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'resources/css/app.css'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'resources/css/filament/backend/theme.css'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'resources/css/filament/member/theme.css'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'resources/js/app.js'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;refresh&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt;


&lt;span class="c1"&gt;// BackendPanelProvider&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'backend'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;viteTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'resources/css/filament/backend/theme.css'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// MemberPanelProvider&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'member'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;viteTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'resources/css/filament/member/theme.css'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;theme.css&lt;/code&gt; is scoped to its own panel by default. The &lt;code&gt;@source&lt;/code&gt; directives only point to that panel's files, and customizing one theme doesn't affect the other. This is how the &lt;a href="https://filamentmastery.com/articles/laravel-filament-multipanel-starter-build-your-app-fast/" rel="noopener noreferrer"&gt;Multipanel&lt;/a&gt; and &lt;a href="https://filamentmastery.com/articles/laravel-filament-multi-tenant-starter-build-your-app-fast/" rel="noopener noreferrer"&gt;Multi-Tenant&lt;/a&gt; starters are set up: two themes, generated empty and scoped, ready for each panel to be styled differently if needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sharing one theme across panels:&lt;/strong&gt;  you don't have to generate a separate file per panel. If two panels should look identical, you can point both panel providers at the same theme file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BackendPanelProvider&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;viteTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'resources/css/filament/shared/theme.css'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// MemberPanelProvider&lt;/span&gt;
&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;viteTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'resources/css/filament/shared/theme.css'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just make sure the &lt;code&gt;@source&lt;/code&gt; paths in that shared file cover both panels directories, since a single generated theme only scopes to the panel it was created for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading Filament's hook classes
&lt;/h2&gt;

&lt;p&gt;Once you start customizing beyond colors, you'll be targeting &lt;a href="https://filamentphp.com/docs/5.x/styling/css-hooks?ref=filamentmastery.com#common-hook-class-abbreviations" rel="noopener noreferrer"&gt;Filament's own CSS classes&lt;/a&gt;, the ones used in the sidebar example below and throughout the interface. They follow a consistent naming convention worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fi&lt;/code&gt; is short for "Filament"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-ac&lt;/code&gt; is used for classes from the Actions package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-fo&lt;/code&gt; is used for classes from the Forms package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-in&lt;/code&gt; is used for classes from the Infolists package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-no&lt;/code&gt; is used for classes from the Notifications package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-sc&lt;/code&gt; is used for classes from the Schema package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-ta&lt;/code&gt; is used for classes from the Tables package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fi-wi&lt;/code&gt; is used for classes from the Widgets package&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;btn&lt;/code&gt; is short for "button"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;col&lt;/code&gt; is short for "column"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ctn&lt;/code&gt; is short for "container"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;wrp&lt;/code&gt; is short for "wrapper"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you know this pattern, hook class names become much easier to read without digging through Filament's source every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding hook classes in practice:&lt;/strong&gt;  the naming convention tells you what a class means once you've found it, but you'll still need to find it first. The fastest way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your browser's dev tools on the panel you want to customize&lt;/li&gt;
&lt;li&gt;Inspect the element you want to target&lt;/li&gt;
&lt;li&gt;Look for classes starting with &lt;code&gt;fi-&lt;/code&gt; in the inspector&lt;/li&gt;
&lt;li&gt;Use that class in your theme's CSS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the most reliable way to target the exact element you're looking at, rather than guessing from the naming convention alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customizing your theme
&lt;/h2&gt;

&lt;p&gt;With the theme registered, you can now add your own styles directly in &lt;code&gt;theme.css&lt;/code&gt;. Filament's hook classes, documented in the &lt;a href="https://filamentphp.com/docs/5.x/styling/overview?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Filament docs&lt;/a&gt;, let you target specific parts of the interface.&lt;/p&gt;

&lt;p&gt;Here's an example customizing the sidebar, the same idea as in the original v3 guide, adapted to the new file structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s2"&gt;'../../../../vendor/filament/filament/resources/css/theme.css'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../app/Filament/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;@source&lt;/span&gt; &lt;span class="s2"&gt;'../../../../resources/views/filament/**/*'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;.fi-sidebar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;!bg-gray-500;&lt;/span&gt;

    &lt;span class="err"&gt;.fi-sidebar-header&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;.fi-icon-btn-icon&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
            &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;text-primary-500;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.fi-sidebar-group-label&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.fi-icon-btn-icon&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.fi-sidebar-group-icon&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.fi-sidebar-item-icon&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.fi-sidebar-item-button&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.fi-sidebar-item-label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;text-white;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.fi-sidebar-item-active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;.fi-sidebar-item-button&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
            &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;bg-primary-500;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.fi-sidebar-item&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.fi-sidebar-item-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;:hover&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
            &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="py"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;bg-primary-500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.fi-icon-btn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;:hover&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt;
            &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="py"&gt;hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;text-primary-500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;.fi-sidebar-nav-groups&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="err"&gt;@apply&lt;/span&gt; &lt;span class="err"&gt;gap-y-0;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@apply&lt;/code&gt; syntax and the hook classes themselves haven't changed. What changed is everything around them: how the theme is generated, how Tailwind scans your files, and how the build is configured.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using CSS variables for reusable values
&lt;/h3&gt;

&lt;p&gt;If you find yourself repeating the same color or value across multiple rules, defining a CSS variable in &lt;code&gt;@layer base&lt;/code&gt; keeps things consistent and easier to update later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@layer&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="py"&gt;--sidebar-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1f2937&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;:root&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;class&lt;/span&gt;&lt;span class="o"&gt;~=&lt;/span&gt;&lt;span class="s1"&gt;"dark"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="py"&gt;--sidebar-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#111827&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then reference it directly in your styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.fi-sidebar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--sidebar-bg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially useful for values you'll reuse in several places, or want to swap between light and dark mode without duplicating rules.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;npm run build&lt;/code&gt; once you're done, and your custom theme is live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes when customizing Filament themes
&lt;/h2&gt;

&lt;p&gt;A few things come up repeatedly when developers start working with Filament themes for the first time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating a theme when &lt;code&gt;-&amp;gt;colors()&lt;/code&gt; is enough.&lt;/strong&gt; If the only goal is changing the primary color, buttons, and sidebar accents, &lt;code&gt;-&amp;gt;colors()&lt;/code&gt; in the panel provider covers that without generating any files. A custom theme is only necessary for structural changes, targeting specific interface elements via hook classes, or using CSS variables for fine-grained control. Generating a theme file when it isn't needed just adds a build step and a file to maintain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting to add custom Blade directories to &lt;code&gt;@source&lt;/code&gt;.&lt;/strong&gt;  The generated theme only scans Filament's own directories by default. Any Tailwind classes used in custom Blade components, Livewire components, or views outside &lt;code&gt;app/Filament&lt;/code&gt; won't be detected unless you explicitly add those paths. Tailwind silently purges the undetected classes, and the issue only shows up at build time when styles randomly stop working in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Styling internal Filament classes instead of hook classes.&lt;/strong&gt;  Filament's hook classes, prefixed with &lt;code&gt;fi-&lt;/code&gt;, are the intended surface for CSS customization. Styling classes without the &lt;code&gt;fi-&lt;/code&gt; prefix means targeting internal implementation details that can change between releases without notice. Filament's own documentation explicitly warns against this: if a hook class you need doesn't exist yet, the recommended path is to open a pull request to add it, not to target internal classes and accept the maintenance risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overriding too much.&lt;/strong&gt;  The more CSS overrides added to a theme, the harder upgrades become. When Filament ships a new version, any structural change to the interface can break rules that were written against the old markup. Prefer color tokens, CSS variables, and targeted hook class overrides before rewriting large sections of the interface. A theme should adapt Filament to a brand, not turn Filament into a completely different application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And of course, editing vendor files directly.&lt;/strong&gt;  Any change made inside &lt;code&gt;vendor/filament&lt;/code&gt; is overwritten the next time &lt;code&gt;composer update&lt;/code&gt; runs. If something in Filament's default theme needs to be overridden, the right place is the custom &lt;code&gt;theme.css&lt;/code&gt; file, using hook classes to target the element. The vendor directory is never the right place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't want to build a theme from scratch?
&lt;/h3&gt;

&lt;p&gt;If building a custom Filament theme isn't where you want to spend your time, Filafly offers premium themes and plugins ready to drop into your panel.&lt;/p&gt;

&lt;p&gt;Use code &lt;strong&gt;MASTERY15&lt;/strong&gt; for &lt;strong&gt;15% off your purchase&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://filafly.com/?utm_source=filamentmastery&amp;amp;utm_medium=affiliate&amp;amp;utm_campaign=affiliates&amp;amp;ref=filamentmastery" rel="noopener noreferrer"&gt;Discover Filafly themes →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Originally published on &lt;a href="https://filamentmastery.com" rel="noopener noreferrer"&gt;Filament Mastery&lt;/a&gt;&lt;/p&gt;

</description>
      <category>filament</category>
      <category>laravel</category>
      <category>styling</category>
      <category>tailwindcss</category>
    </item>
    <item>
      <title>Extending Filament exports with Laravel Excel</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Wed, 17 Jun 2026 15:49:47 +0000</pubDate>
      <link>https://dev.to/filamentmastery/extending-filament-exports-with-laravel-excel-35db</link>
      <guid>https://dev.to/filamentmastery/extending-filament-exports-with-laravel-excel-35db</guid>
      <description>&lt;p&gt;Filament's export action is great. It's quick to set up, supports queued exports, includes column mapping, handles notifications, and keeps a history of generated files through the &lt;code&gt;Export&lt;/code&gt; model. For most use cases, it's exactly what you need.&lt;/p&gt;

&lt;p&gt;But I recently ran into a limitation that the native export couldn't solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  When XLSX isn't really Excel
&lt;/h2&gt;

&lt;p&gt;I was exporting financial data or measurements from a Filament table. The export worked. The file downloaded. Excel opened it without any issue.&lt;/p&gt;

&lt;p&gt;The problem was that every amount was exported as text instead of a real numeric value.&lt;/p&gt;

&lt;p&gt;For an accountant, that creates several problems immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excel formulas such as &lt;code&gt;=SUM()&lt;/code&gt; don't work correctly&lt;/li&gt;
&lt;li&gt;Selecting a range of cells doesn't display totals in Excel's status bar&lt;/li&gt;
&lt;li&gt;Conditional formatting based on numeric values becomes unreliable&lt;/li&gt;
&lt;li&gt;Additional manual cleanup is required before the file can be used&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Technically the export contained the data. Practically, it wasn't usable.&lt;/p&gt;

&lt;p&gt;The root cause is simple: Filament's export system is designed around CSV-style exports. That's perfect for many scenarios, but it doesn't expose the full spreadsheet capabilities offered by PhpSpreadsheet and &lt;a href="https://laravel-excel.com/?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Laravel Excel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On top of that, I also had a second, completely different requirement: a yearly report with one worksheet per month, merged headers, borders, conditional formatting, and custom layouts. Not a table dump but a report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use Laravel Excel directly?
&lt;/h2&gt;

&lt;p&gt;Laravel Excel already solves all of these problems. It's built on PhpSpreadsheet and provides complete control over cell types, number formats, formulas, styling, and multiple worksheets.&lt;/p&gt;

&lt;p&gt;The obvious solution would have been to abandon Filament's export action entirely and build custom exports from scratch. But that means losing everything Filament already provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Export modal and options form&lt;/li&gt;
&lt;li&gt;Column mapping UI&lt;/li&gt;
&lt;li&gt;Queue handling&lt;/li&gt;
&lt;li&gt;Progress notifications&lt;/li&gt;
&lt;li&gt;Download links&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Export&lt;/code&gt; model history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn't want to rebuild all of that. I simply wanted a different file generator underneath.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full article is available on &lt;a href="https://filamentmastery.com/articles/extending-filament-exports-with-laravel-excel/" rel="noopener noreferrer"&gt;Filament Mastery&lt;/a&gt; for free members - create a free account to access the complete code.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>filament</category>
      <category>laravel</category>
      <category>excel</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Adding a full docker setup to the Filament Mastery Starters</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Fri, 29 May 2026 06:57:02 +0000</pubDate>
      <link>https://dev.to/filamentmastery/adding-a-full-docker-setup-to-the-filament-mastery-starters-258i</link>
      <guid>https://dev.to/filamentmastery/adding-a-full-docker-setup-to-the-filament-mastery-starters-258i</guid>
      <description>&lt;p&gt;For a while, my starter kits didn't include any Docker configuration. The foundation was solid with auth, roles, MFA, Horizon, Logs Viewer, but the deployment side was left to whoever cloned the project.&lt;/p&gt;

&lt;p&gt;That was a deliberate choice at first. Docker setups vary a lot depending on the infrastructure: some people use a reverse proxy, others have Cloudflare in front, some run on bare metal, others on managed platforms. I didn't want to ship something that would need to be ripped out immediately.&lt;/p&gt;

&lt;p&gt;But over time I changed my mind. Here's why and what the process taught me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with "just configure it yourself"
&lt;/h2&gt;

&lt;p&gt;Leaving deployment out of a starter kit sounds reasonable. In practice, it means every project starts with the same 4-6 hours of Docker work that never really changes.&lt;/p&gt;

&lt;p&gt;Multi-stage Dockerfile. PHP-FPM config. Nginx with HTTPS. PostgreSQL and Redis wired up. Horizon and the scheduler running as proper services. Healthchecks everywhere so Docker knows when things are actually ready.&lt;/p&gt;

&lt;p&gt;None of it is so complicated. But it's time-consuming, easy to get subtly wrong, and almost identical from one project to the next.&lt;/p&gt;

&lt;p&gt;Once I admitted that, the question wasn't whether to include Docker, it was how to do it in a way that's actually useful without being too opinionated about production infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I ended up building
&lt;/h2&gt;

&lt;p&gt;The setup I settled on covers the full local development stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A  &lt;strong&gt;multi-stage Dockerfile&lt;/strong&gt; : separate stages for Composer dependencies, Node assets, and the final PHP-FPM image. Keeps the production image lean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx&lt;/strong&gt;  with HTTP-to-HTTPS redirect and a self-signed certificate for local dev, already included, no setup needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL and Redis&lt;/strong&gt;  as services with proper healthchecks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Horizon and the scheduler&lt;/strong&gt;  as dedicated services, not crammed into the main app container.&lt;/li&gt;
&lt;li&gt;A  &lt;strong&gt;bootstrap service&lt;/strong&gt;  that runs &lt;code&gt;php artisan migrate --force&lt;/code&gt; before the app starts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Dockerfile uses three stages to keep the final image as lean as possible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;php:8.4-fpm-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;composer_builder&lt;/span&gt;
&lt;span class="c"&gt;# Install extensions, run composer install&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:24-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node_builder&lt;/span&gt;
&lt;span class="c"&gt;# Install npm dependencies, build Vite assets&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;php:8.4-fpm-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;php_fpm&lt;/span&gt;
&lt;span class="c"&gt;# Final image, only what's needed to run&lt;/span&gt;
&lt;span class="c"&gt;# Copy vendor/ from composer_builder&lt;/span&gt;
&lt;span class="c"&gt;# Copy public/build/ from node_builder&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each stage does one thing. The final image never contains Composer, Node, or dev dependencies.&lt;/p&gt;

&lt;p&gt;The full Dockerfile architecture with extensions, non-root user, Xdebug for local dev, is covered here: &lt;a href="https://filamentmastery.com/articles/production-ready-docker-setup-for-laravel-filament/" rel="noopener noreferrer"&gt;Production-Ready Docker Setup for Laravel Filament&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bootstrap service
&lt;/h2&gt;

&lt;p&gt;Running migrations on deploy is one of those things that sounds simple until you've had a deployment fail because the app started before the database was ready.&lt;/p&gt;

&lt;p&gt;The pattern I use is a dedicated &lt;code&gt;bootstrap&lt;/code&gt; service that exits when migrations succeed. The &lt;code&gt;app&lt;/code&gt; service depends on it, so the app simply doesn't start until migrations are done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${APP_IMAGE}:${APP_VERSION}&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;php artisan migrate --force&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${APP_IMAGE}:${APP_VERSION}&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_completed_successfully&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="na"&gt;horizon&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${APP_IMAGE}:${APP_VERSION}&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;php artisan horizon&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_completed_successfully&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="na"&gt;scheduler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${APP_IMAGE}:${APP_VERSION}&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;php artisan schedule:work&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No SSH. No manual commands. No "did someone run the migrations?" before going live.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A word of honesty on this pattern:&lt;/strong&gt;  it works well for single-instance deployments, one VPS, one app container. If you're running multiple replicas or need strict zero-downtime guarantees, this approach has limits. Multiple bootstrap services running simultaneously can conflict, and the app will be briefly unavailable during migration. In those cases, migrations should be handled at the CI/CD pipeline level, before containers are deployed. That's a topic worth a dedicated article, and it's on the roadmap.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The full compose setup, volumes, healthchecks, network config, restart policies, is covered in detail here: &lt;a href="https://filamentmastery.com/articles/production-ready-docker-compose-for-laravel-filament/" rel="noopener noreferrer"&gt;Production-Ready Docker Compose for Laravel Filament&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I deliberately left out
&lt;/h2&gt;

&lt;p&gt;I didn't include a production-ready Nginx config. Not because it's hard to write, but because production environments vary too much.&lt;/p&gt;

&lt;p&gt;Some projects sit behind Traefik. Others use Cloudflare in front. Some have real Let's Encrypt certificates managed externally, others use internal PKI. Shipping a "production" Nginx config that works for one setup and silently breaks another isn't helpful.&lt;/p&gt;

&lt;p&gt;What I do ship is a &lt;code&gt;docker-compose.example.yaml&lt;/code&gt;, clearly labeled as a starting point, not a drop-in solution. The dev config is complete and ready to use. The production side is documented, commented, and deliberately left for the developer to adapt.&lt;/p&gt;

&lt;p&gt;I think that's the right balance for a starter kit. Give people enough to be productive immediately, without making decisions that belong to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes for the starters
&lt;/h2&gt;

&lt;p&gt;Both the &lt;a href="https://filamentmastery.com/articles/laravel-filament-backend-starter-build-your-admin-panel-fast/" rel="noopener noreferrer"&gt;Backend Starter&lt;/a&gt; and the &lt;a href="https://filamentmastery.com/articles/laravel-filament-multipanel-starter-build-your-app-fast/" rel="noopener noreferrer"&gt;Multipanel Starter&lt;/a&gt; now include the full Docker setup.&lt;/p&gt;

&lt;p&gt;Copy the repo, copy &lt;code&gt;.env.example&lt;/code&gt;, define your app key, run&lt;code&gt;docker compose up -d --build&lt;/code&gt;, then &lt;code&gt;php artisan backend:setup&lt;/code&gt;, and you have a running panel with auth, roles, MFA, Horizon, and Logs Viewer, all in one go.&lt;/p&gt;

&lt;p&gt;It's a meaningful improvement over "configure Docker yourself." Not because Docker is complicated, but because those 4-6 hours are better spent on the actual project.&lt;/p&gt;

&lt;p&gt;Both starters are available with the &lt;a href="https://filamentmastery.com/membership/" rel="noopener noreferrer"&gt;Filament Mastery membership&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As always, if something doesn't work the way you'd expect or you'd approach it differently, let me know in the comments.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>filament</category>
      <category>php</category>
      <category>docker</category>
    </item>
    <item>
      <title>Implement SAML SSO Authentication in Laravel Filament with Socialite</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Wed, 20 May 2026 11:10:04 +0000</pubDate>
      <link>https://dev.to/filamentmastery/implement-saml-sso-authentication-in-laravel-filament-with-socialite-1m8k</link>
      <guid>https://dev.to/filamentmastery/implement-saml-sso-authentication-in-laravel-filament-with-socialite-1m8k</guid>
      <description>&lt;p&gt;Single Sign-On (SSO) is a common requirement in enterprise applications.&lt;/p&gt;

&lt;p&gt;When working with Laravel Filament in internal business environments, clients often want to authenticate users directly through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active Directory&lt;/li&gt;
&lt;li&gt;Microsoft Entra ID (Azure AD)&lt;/li&gt;
&lt;li&gt;Okta&lt;/li&gt;
&lt;li&gt;Keycloak&lt;/li&gt;
&lt;li&gt;Google Workspace&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of managing passwords inside your application.&lt;/p&gt;

&lt;p&gt;In this article, we’ll build a clean SAML SSO integration using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Laravel Socialite&lt;/li&gt;
&lt;li&gt;The Socialite SAML2 provider&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not only to make authentication work, but also to build a maintainable foundation for enterprise environments.&lt;/p&gt;

&lt;p&gt;At the end of this article, you’ll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SAML authentication working inside Filament&lt;/li&gt;
&lt;li&gt;A metadata endpoint for your Identity Provider&lt;/li&gt;
&lt;li&gt;A dedicated authentication flow&lt;/li&gt;
&lt;li&gt;A clean architecture ready for role synchronization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the &lt;a href="https://filamentmastery.com/articles/production-ready-active-directory-role-mapping-in-laravel-filament/" rel="noopener noreferrer"&gt;premium follow-up article&lt;/a&gt;, we’ll implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active Directory group mapping&lt;/li&gt;
&lt;li&gt;Spatie Permission synchronization&lt;/li&gt;
&lt;li&gt;Production-ready role handling&lt;/li&gt;
&lt;li&gt;Enterprise access control strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installing the SAML2 Provider
&lt;/h2&gt;

&lt;p&gt;First, install Laravel Socialite and the SAML2 provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require laravel/socialite
composer require socialiteproviders/saml2

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then configure your SAML provider inside &lt;code&gt;config/services.php&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the SAML Provider
&lt;/h2&gt;

&lt;p&gt;Here is a simple SAML configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'saml2'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'metadata'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'SAML2_METADATA_URL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'http://localhost:4000/api/saml/metadata'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'sp_acs'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'auth/saml2/callback'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'sp_default_binding_method'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;\LightSaml\SamlConstants&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;BINDING_SAML2_HTTP_POST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'sp_name_id_format'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Depending on your security requirements, you may also need signed or encrypted assertions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Understanding the Configuration
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;metadata&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This is the Identity Provider metadata URL.&lt;/p&gt;

&lt;p&gt;Depending on your environment, this could come from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Microsoft Entra ID&lt;/li&gt;
&lt;li&gt;Okta&lt;/li&gt;
&lt;li&gt;Keycloak&lt;/li&gt;
&lt;li&gt;A local SAML testing provider&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The provider uses this metadata to retrieve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;certificates&lt;/li&gt;
&lt;li&gt;SSO endpoints&lt;/li&gt;
&lt;li&gt;bindings&lt;/li&gt;
&lt;li&gt;entity identifiers&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;sp_acs&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This is your Assertion Consumer Service (ACS) endpoint.&lt;/p&gt;

&lt;p&gt;After authentication, the Identity Provider redirects the user back to this endpoint.&lt;/p&gt;

&lt;p&gt;In our case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/auth/saml2/callback

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;sp_default_binding_method&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;This defines how SAML responses are transmitted.&lt;/p&gt;

&lt;p&gt;Using POST binding is generally the safest and most common option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Controller
&lt;/h2&gt;

&lt;p&gt;A dedicated controller keeps the authentication flow clean and isolated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers\Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Services\SAML2Service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Filament\Notifications\Notification&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\RedirectResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SAML2Controller&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;SAML2Service&lt;/span&gt; &lt;span class="nv"&gt;$saml2Service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;saml2Service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;RedirectResponse&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;saml2Service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;RedirectResponse&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;saml2Service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Notification&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Unable to connect. Please contact an administrator.'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;danger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;filament&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getUrl&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Use a Dedicated Service?
&lt;/h3&gt;

&lt;p&gt;Many authentication tutorials place all the logic directly inside the controller.&lt;/p&gt;

&lt;p&gt;This quickly becomes difficult to maintain when adding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;role synchronization&lt;/li&gt;
&lt;li&gt;multiple providers&lt;/li&gt;
&lt;li&gt;access policies&lt;/li&gt;
&lt;li&gt;audit logging&lt;/li&gt;
&lt;li&gt;tenant support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Moving the SAML logic into a dedicated service keeps the architecture maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the Routes
&lt;/h2&gt;

&lt;p&gt;Next, create the authentication web routes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'auth/saml2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'auth.saml2.'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\App\Http\Controllers\Auth\SAML2Controller&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'metadata'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'metadata'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'metadata'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'redirect'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'redirect'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'login'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'callback'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'callback'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'callback'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/auth/saml2/metadata : Service Provider metadata&lt;/li&gt;
&lt;li&gt;/auth/saml2/redirect : Redirect to Identity Provider&lt;/li&gt;
&lt;li&gt;/auth/saml2/callback : Handle SAML response&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building the SAML Service
&lt;/h2&gt;

&lt;p&gt;Now let’s implement the service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Filament\Facades\Filament&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Laravel\Socialite\Facades\Socialite&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;SocialiteProviders\Saml2\Provider&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\RedirectResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SAML2Service&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;RedirectResponse&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'saml2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;?User&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cd"&gt;/** @var Provider $saml2Provider */&lt;/span&gt;
        &lt;span class="nv"&gt;$saml2Provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'saml2'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="cd"&gt;/** @var \SocialiteProviders\Saml2\User $socialiteUser */&lt;/span&gt;
        &lt;span class="nv"&gt;$socialiteUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$saml2Provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;stateless&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;updateOrCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;strtolower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$socialiteUser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getEmail&lt;/span&gt;&lt;span class="p"&gt;())],&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$socialiteUser&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;canAccessPanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Filament&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getCurrentOrDefaultPanel&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="cd"&gt;/** @var Provider $saml2Provider */&lt;/span&gt;
        &lt;span class="nv"&gt;$saml2Provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'saml2'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$saml2Provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getServiceProviderMetadata&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding the Authentication Flow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Redirecting the User
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Socialite&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'saml2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sends the user to the Identity Provider login page.&lt;/p&gt;

&lt;p&gt;Depending on the environment, users may already be authenticated through their corporate session.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieving the Authenticated User
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$socialiteUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$saml2Provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;stateless&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The provider validates the SAML response and extracts the user information.&lt;/p&gt;

&lt;p&gt;At this stage, you usually receive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;email&lt;/li&gt;
&lt;li&gt;first name&lt;/li&gt;
&lt;li&gt;last name&lt;/li&gt;
&lt;li&gt;groups&lt;/li&gt;
&lt;li&gt;claims&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact payload depends on your Identity Provider configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating or Updating the User
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;updateOrCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach allows users to authenticate without pre-creating accounts manually.&lt;/p&gt;

&lt;p&gt;It also keeps user information synchronized automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restricting Filament Access
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;canAccessPanel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an important step.&lt;/p&gt;

&lt;p&gt;Authenticating a user does not necessarily mean they should access your Filament panel.&lt;/p&gt;

&lt;p&gt;By checking panel access explicitly, you keep your authorization layer consistent with the rest of your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating Service Provider Metadata
&lt;/h2&gt;

&lt;p&gt;One of the most useful features of the provider is automatic metadata generation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$saml2Provider&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getServiceProviderMetadata&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This endpoint can be shared directly with your Identity Provider administrator.&lt;/p&gt;

&lt;p&gt;It avoids:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;manually crafting XML files&lt;/li&gt;
&lt;li&gt;configuration mistakes&lt;/li&gt;
&lt;li&gt;certificate inconsistencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In many enterprise environments, this alone saves a significant amount of setup time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improving the Login Experience in Filament
&lt;/h2&gt;

&lt;p&gt;You can now add a custom login button inside your Filament login page.&lt;/p&gt;

&lt;p&gt;For example with a render hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;FilamentView&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;registerRenderHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;PanelsRenderHook&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;AUTH_LOGIN_FORM_BEFORE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'filament.components.saml-login-button'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;In provider file (like AppServiceProvider.php boot function)&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;x-filament::button&lt;/span&gt;
    &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ route('auth.saml2.login') }}"&lt;/span&gt;
    &lt;span class="na"&gt;tag=&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt;
    &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;
    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"w-full"&lt;/span&gt;
    &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"heroicon-o-arrow-right-circle"&lt;/span&gt;
    &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Connect with your SSO account"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    {{ __('Connect with your SSO account') }}
&lt;span class="nt"&gt;&amp;lt;/x-filament::button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;In view file filament.components.saml-login-button.blade.php&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This provides a much cleaner enterprise login experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;For local development and testing, I personally use &lt;a href="https://github.com/ory/mocksaml?ref=filamentmastery.com" rel="noopener noreferrer"&gt;MockSAML&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;This is also why the default metadata configuration points to port &lt;code&gt;4000&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'metadata'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'SAML2_METADATA_URL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'http://localhost:4000/api/saml/metadata'&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using a lightweight mock Identity Provider makes it much easier to test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SAML authentication flows&lt;/li&gt;
&lt;li&gt;user provisioning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without requiring access to a real enterprise Active Directory during development.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About Active Directory Groups?
&lt;/h2&gt;

&lt;p&gt;At this point, authentication works.&lt;/p&gt;

&lt;p&gt;However, most enterprise applications also need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;role synchronization with Active Directory group mapping&lt;/li&gt;
&lt;li&gt;dynamic authorization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where things become significantly more interesting.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://filamentmastery.com/articles/production-ready-active-directory-role-mapping-in-laravel-filament/" rel="noopener noreferrer"&gt;premium article&lt;/a&gt;, we’ll implement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automatic role synchronization&lt;/li&gt;
&lt;li&gt;group parsing from SAML claims&lt;/li&gt;
&lt;li&gt;Active Directory CN extraction&lt;/li&gt;
&lt;li&gt;role mapping strategies&lt;/li&gt;
&lt;li&gt;production-ready access control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SAML authentication is often perceived as complex, but Laravel Socialite combined with the SAML2 provider makes the integration surprisingly clean.&lt;/p&gt;

&lt;p&gt;By isolating the authentication flow into a dedicated service and integrating directly with Filament, you can build enterprise-ready authentication while keeping your application maintainable.&lt;/p&gt;

&lt;p&gt;The next step is implementing proper authorization and Active Directory role synchronization.&lt;/p&gt;

&lt;p&gt;To receive more articles like this one, subscribe to &lt;a href="https://filamentmastery.com/#/portal/signup" rel="noopener noreferrer"&gt;Filament Mastery&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>security</category>
      <category>advanced</category>
      <category>filament</category>
      <category>sso</category>
    </item>
    <item>
      <title>Improve Filament Import UX with Persistent Error CSV Downloads</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Sun, 10 May 2026 17:10:31 +0000</pubDate>
      <link>https://dev.to/filamentmastery/improve-filament-import-ux-with-persistent-error-csv-downloads-54jj</link>
      <guid>https://dev.to/filamentmastery/improve-filament-import-ux-with-persistent-error-csv-downloads-54jj</guid>
      <description>&lt;p&gt;By default, FilamentPHP provides a convenient &lt;a href="https://filamentphp.com/docs/5.x/actions/import?ref=filamentmastery.com" rel="noopener noreferrer"&gt;import action&lt;/a&gt; with built-in feedback once the process is complete.&lt;/p&gt;

&lt;p&gt;After an import finishes, a notification is displayed showing the result, along with a link to download a CSV file containing failed rows.&lt;/p&gt;

&lt;p&gt;While this works well for simple use cases, it quickly becomes limiting in real-world applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only the user who initiated the import can access the error file&lt;/li&gt;
&lt;li&gt;The download link disappears once the notification is dismissed&lt;/li&gt;
&lt;li&gt;There is no built-in way to view past imports or retry analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 In a team environment, this can become frustrating very quickly.&lt;/p&gt;

&lt;p&gt;In this article, we'll implement a simple and clean solution to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;List all imports in a Filament resource&lt;/li&gt;
&lt;li&gt;Allow authorized users to download error CSV files&lt;/li&gt;
&lt;li&gt;Manage and delete past imports&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding Filament's Default Behavior
&lt;/h2&gt;

&lt;p&gt;Filament already provides an internal model to handle imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Filament\Actions\Imports\Models\Import&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This model includes several useful attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;completed_at&lt;/code&gt; → timestamp&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;processed_rows&lt;/code&gt; → integer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;total_rows&lt;/code&gt; → integer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;successful_rows&lt;/code&gt; → integer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also exposes computed values like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$import&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getFailedRowsCount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filament also defines an internal route used to download failed rows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/imports/{import}/failed-rows/download'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DownloadImportFailureCsv&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'imports.failed-rows.download'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't need to override this route, we'll simply control access to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Access Problem
&lt;/h2&gt;

&lt;p&gt;By default, if no Policy is defined, Filament restricts access like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$importPolicy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Gate&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;getPolicyFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$import&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;filled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$importPolicy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;method_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$importPolicy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'view'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Gate&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;forUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'view'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Arr&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$import&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;abort_unless&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$import&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Extract of Filament\Actions\Imports\Http\Controllers\DownloadImportFailureCsv Controller&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If no policy exists → only the owner can download the file&lt;/li&gt;
&lt;li&gt;If a policy exists → Filament defers authorization to it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the solution is simple:  &lt;strong&gt;define a Policy&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Import Policy
&lt;/h2&gt;

&lt;p&gt;Generate the policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:policy ImportPolicy &lt;span class="nt"&gt;--model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Filament&lt;/span&gt;&lt;span class="se"&gt;\A&lt;/span&gt;&lt;span class="s2"&gt;ctions&lt;/span&gt;&lt;span class="se"&gt;\I&lt;/span&gt;&lt;span class="s2"&gt;mports&lt;/span&gt;&lt;span class="se"&gt;\M&lt;/span&gt;&lt;span class="s2"&gt;odels&lt;/span&gt;&lt;span class="se"&gt;\I&lt;/span&gt;&lt;span class="s2"&gt;mport"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is an example implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Policies&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Enums\ImportImporterEnum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Enums\PermissionEnum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Models\User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Services\PermissionService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Filament\Actions\Imports\Models\Import&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Auth\Access\HandlesAuthorization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImportPolicy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;HandlesAuthorization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;PermissionService&lt;/span&gt; &lt;span class="nv"&gt;$permissionService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;viewAny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasPermissionTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;permissionService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPermissionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PermissionEnum&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VIEW_ANY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Import&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Import&lt;/span&gt; &lt;span class="nv"&gt;$import&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;is_null&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$import&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;completed_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$import&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getFailedRowsCount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasPermissionTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;permissionService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPermissionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PermissionEnum&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;VIEW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Import&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Import&lt;/span&gt; &lt;span class="nv"&gt;$import&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$import&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;importer&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;ImportImporterEnum&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;PRODUCT_MAPPING&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;hasPermissionTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;permissionService&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getPermissionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PermissionEnum&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DELETE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Import&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In this example, permissions are handled using a role/permission system (e.g. &lt;a href="https://spatie.be/docs/laravel-permission/v7/introduction?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Spatie Laravel Permission&lt;/a&gt;) with a own Permission Service and Enum. Adapt this logic to your own application.&lt;/p&gt;

&lt;p&gt;Ideally, Filament would use a dedicated &lt;code&gt;download&lt;/code&gt; method instead of &lt;code&gt;view&lt;/code&gt;, as these represent different concerns. However, we'll stick with the default behavior for simplicity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Registering the Policy
&lt;/h2&gt;

&lt;p&gt;Don't forget to register the policy in your service provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Filament\Actions\Imports\Models\Import&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;boot&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Gate&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Import&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ImportPolicy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="mf"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the Filament Resource
&lt;/h2&gt;

&lt;p&gt;Now let’s expose imports in the admin panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:filament-resource Import &lt;span class="nt"&gt;--model-namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Filament&lt;span class="se"&gt;\\&lt;/span&gt;Actions&lt;span class="se"&gt;\\&lt;/span&gt;Imports&lt;span class="se"&gt;\\&lt;/span&gt;Models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We don’t need forms here, only a listing page. So you can delete &lt;code&gt;Schemas&lt;/code&gt; format and some pages like  &lt;code&gt;CreateImport&lt;/code&gt; and &lt;code&gt;EditImport&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhk238d2be91nujda93x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzhk238d2be91nujda93x.png" alt="Improve Filament Import UX with Persistent Error CSV Downloads" width="542" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Resource class
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImportResource&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Resource&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;?string&lt;/span&gt; &lt;span class="nv"&gt;$model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Import&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Table&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Table&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ImportsTable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getPages&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'index'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;ListImports&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Simplified Import Resource&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Imports Table
&lt;/h2&gt;

&lt;p&gt;Here's a simplified version of the table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImportsTable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Table&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Table&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$table&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="nc"&gt;TextColumn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'file_name'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;searchable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="nc"&gt;TextColumn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'importer'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nc"&gt;TextColumn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'user.name'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nc"&gt;TextColumn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'total_rows'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="nc"&gt;TextColumn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'processed_rows'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="nc"&gt;TextColumn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'successful_rows'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="nc"&gt;TextColumn&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'completed_at'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;sortable&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;recordActions&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="nc"&gt;ActionGroup&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                    &lt;span class="nc"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'downloadFailedRowsCsv'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Download errors'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'warning'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Heroicon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;ArrowDownCircle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Import&lt;/span&gt; &lt;span class="nv"&gt;$import&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;signedRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'filament.imports.failed-rows.download'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'authGuard'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;filament&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getAuthGuard&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s1"&gt;'import'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$import&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;absolute&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;shouldOpenInNewTab&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'view'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

                    &lt;span class="nc"&gt;DeleteAction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;defaultSort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'created_at'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'desc'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Simplified ImportsTable&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Feature: Downloading Failed Rows
&lt;/h2&gt;

&lt;p&gt;The most important part is this action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'downloadFailedRowsCsv'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uses Filament's internal signed route&lt;/li&gt;
&lt;li&gt;Relies on the &lt;code&gt;view&lt;/code&gt; policy for authorization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Combined with the policy, this ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only authorized users can access error files&lt;/li&gt;
&lt;li&gt;Downloads remain secure&lt;/li&gt;
&lt;li&gt;UX is significantly improved&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final Result
&lt;/h2&gt;

&lt;p&gt;With just a Policy and a Resource, you now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A complete history of imports&lt;/li&gt;
&lt;li&gt;Persistent access to error CSV files&lt;/li&gt;
&lt;li&gt;Team-friendly access control&lt;/li&gt;
&lt;li&gt;A cleaner and more professional admin experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kq6fiir75pwf3jfeg7b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0kq6fiir75pwf3jfeg7b.png" alt="Improve Filament Import UX with Persistent Error CSV Downloads" width="800" height="404"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Import's actions with failed rows&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjnudgkhdogw2uhnfv8x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjnudgkhdogw2uhnfv8x.png" alt="Improve Filament Import UX with Persistent Error CSV Downloads" width="800" height="404"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Import's actions without failed rows&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Filament provides powerful building blocks, but some features, like import history, require a bit of customization to fully shine.&lt;/p&gt;

&lt;p&gt;With this approach, you enhance both usability and maintainability without adding unnecessary complexity.&lt;/p&gt;

&lt;p&gt;From here, you could go further by adding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retry mechanisms&lt;/li&gt;
&lt;li&gt;Import status filters&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🚀 &lt;strong&gt;Want to go further?&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;More articles on &lt;a href="https://filamentmastery.com/" rel="noopener noreferrer"&gt;Filament Mastery&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’m sharing production-ready Laravel &amp;amp; Filament setups too (Docker, CI/CD, deployment…).&lt;br&gt;
&lt;a href="https://filamentmastery.com/#/portal" rel="noopener noreferrer"&gt;Join the Early Supporters tier for full access&lt;/a&gt;&lt;/p&gt;

</description>
      <category>filament</category>
      <category>adminpanels</category>
      <category>laravel</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Production Mindset - Laravel with Docker Compose</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Wed, 06 May 2026 07:49:26 +0000</pubDate>
      <link>https://dev.to/filamentmastery/production-mindset-laravel-with-docker-compose-125i</link>
      <guid>https://dev.to/filamentmastery/production-mindset-laravel-with-docker-compose-125i</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/filamentmastery/production-ready-docker-setup-for-laravel-filament-2j9"&gt;previous article&lt;/a&gt;, I showed how to build a production-ready Docker image for Laravel &amp;amp; Filament.&lt;/p&gt;

&lt;p&gt;But in real-world applications, a single container is never enough.&lt;/p&gt;

&lt;p&gt;Running a production application means dealing with multiple concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;web server&lt;/li&gt;
&lt;li&gt;PHP runtime&lt;/li&gt;
&lt;li&gt;database&lt;/li&gt;
&lt;li&gt;cache&lt;/li&gt;
&lt;li&gt;background workers&lt;/li&gt;
&lt;li&gt;scheduled jobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this is where things usually start to get messy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with "simple" setups
&lt;/h2&gt;

&lt;p&gt;Most Docker Compose examples look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one container&lt;/li&gt;
&lt;li&gt;maybe a database&lt;/li&gt;
&lt;li&gt;everything else mixed together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It works.&lt;/p&gt;

&lt;p&gt;Until it doesn’t.&lt;/p&gt;

&lt;p&gt;From my experience, this approach quickly leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;poor observability&lt;/li&gt;
&lt;li&gt;difficult debugging&lt;/li&gt;
&lt;li&gt;no real scaling strategy&lt;/li&gt;
&lt;li&gt;fragile deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A simple rule I follow
&lt;/h2&gt;

&lt;p&gt;Over time, I ended up following one principle:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;one container = one responsibility&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It sounds simple, but it completely changes how you design your architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  A production-oriented architecture
&lt;/h2&gt;

&lt;p&gt;Instead of one container doing everything, I split responsibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app&lt;/code&gt; → PHP-FPM runtime
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;web&lt;/code&gt; → reverse proxy (Nginx)
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;db&lt;/code&gt; → PostgreSQL
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;redis&lt;/code&gt; → cache &amp;amp; queues
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;horizon&lt;/code&gt; → queue workers
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scheduler&lt;/code&gt; → scheduled jobs
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each service is isolated.&lt;/p&gt;

&lt;p&gt;Each service can scale independently.&lt;/p&gt;

&lt;p&gt;Each service can fail independently.&lt;/p&gt;

&lt;p&gt;👉 This is what makes the system predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  A common trap: the "public volume"
&lt;/h2&gt;

&lt;p&gt;One mistake I’ve seen (and made) multiple times is how static assets are handled.&lt;/p&gt;

&lt;p&gt;A typical setup uses:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;public:/var/www/app/public&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Looks fine.&lt;/p&gt;

&lt;p&gt;But in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;old assets can persist between deployments&lt;/li&gt;
&lt;li&gt;new builds may not be reflected&lt;/li&gt;
&lt;li&gt;deployments become inconsistent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 This kind of issue is subtle… and painful in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Another key point: bootstrap vs runtime
&lt;/h2&gt;

&lt;p&gt;In many setups, migrations and initialization tasks run inside the main container.&lt;/p&gt;

&lt;p&gt;I prefer separating concerns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;runtime containers → long-running processes&lt;/li&gt;
&lt;li&gt;bootstrap → one-time tasks (migrations, setup)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 In real production systems, this is often handled in CI/CD pipelines instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;This kind of architecture gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;better isolation&lt;/li&gt;
&lt;li&gt;clearer debugging&lt;/li&gt;
&lt;li&gt;safer deployments&lt;/li&gt;
&lt;li&gt;real scalability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not the simplest setup.&lt;/p&gt;

&lt;p&gt;But it’s much closer to what you actually need in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going further
&lt;/h2&gt;

&lt;p&gt;In this article, I intentionally kept things focused on concepts and architecture.&lt;/p&gt;

&lt;p&gt;👉 I cover the full &lt;a href="https://filamentmastery.com/articles/production-ready-docker-compose-for-laravel-filament/" rel="noopener noreferrer"&gt;Docker Compose setup&lt;/a&gt; (with real configuration, volumes, healthchecks, and production patterns)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://filamentmastery.com/articles/production-ready-docker-compose-for-laravel-filament/" rel="noopener noreferrer"&gt;https://filamentmastery.com/articles/production-ready-docker-compose-for-laravel-filament/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Series
&lt;/h2&gt;

&lt;p&gt;This article is part of a series on production-ready Laravel &amp;amp; Filament setups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker image (multi-stage build)&lt;/li&gt;
&lt;li&gt;Docker Compose architecture&lt;/li&gt;
&lt;li&gt;CI/CD pipelines&lt;/li&gt;
&lt;li&gt;deployment strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ll be covering each part progressively.&lt;/p&gt;




&lt;p&gt;If you’ve already built similar setups, I’d be curious to hear how you structure your containers in production.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>docker</category>
      <category>filament</category>
      <category>devops</category>
    </item>
    <item>
      <title>Production-Ready Docker Setup for Laravel &amp; Filament</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Tue, 28 Apr 2026 09:46:47 +0000</pubDate>
      <link>https://dev.to/filamentmastery/production-ready-docker-setup-for-laravel-filament-2j9</link>
      <guid>https://dev.to/filamentmastery/production-ready-docker-setup-for-laravel-filament-2j9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Most Docker setups for Laravel applications are… fine.&lt;/p&gt;

&lt;p&gt;They work locally, they run &lt;code&gt;php artisan serve&lt;/code&gt;, and that’s about it.&lt;/p&gt;

&lt;p&gt;But when you start building real-world Laravel &amp;amp; Filament applications used in production, those setups quickly become a liability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;slow builds&lt;/li&gt;
&lt;li&gt;bloated images&lt;/li&gt;
&lt;li&gt;security issues&lt;/li&gt;
&lt;li&gt;poor separation between development and production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve personally run into these issues multiple times before refining this setup.&lt;/p&gt;

&lt;p&gt;👉 This article focuses on the core ideas and architecture.&lt;br&gt;
The full production-ready implementation (with complete Dockerfile, edge cases and CI/CD integration) is available on &lt;a href="https://filamentmastery.com/" rel="noopener noreferrer"&gt;Filament Mastery&lt;/a&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  What This Article Covers
&lt;/h2&gt;

&lt;p&gt;In this article, I’ll walk through the key concepts behind a Docker setup I use in production for Laravel &amp;amp; Filament projects.&lt;/p&gt;

&lt;p&gt;This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multi-stage builds&lt;/li&gt;
&lt;li&gt;optimized dependency installation&lt;/li&gt;
&lt;li&gt;frontend asset compilation&lt;/li&gt;
&lt;li&gt;non-root execution for better security&lt;/li&gt;
&lt;li&gt;environment-specific configuration (dev vs production)&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  The Problem with “Basic Docker Setups”
&lt;/h2&gt;

&lt;p&gt;A typical Dockerfile often looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; php:8.4-fpm&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple… but problematic.&lt;/p&gt;

&lt;p&gt;Main issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installs dev dependencies in production&lt;/li&gt;
&lt;li&gt;no asset compilation strategy&lt;/li&gt;
&lt;li&gt;runs as root&lt;/li&gt;
&lt;li&gt;large and slow images&lt;/li&gt;
&lt;li&gt;no separation of concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 This is fine for learning, but not for production.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Better Approach
&lt;/h2&gt;

&lt;p&gt;Instead of a single container doing everything, I rely on multi-stage builds and clear separation of responsibilities.&lt;/p&gt;

&lt;p&gt;Typical structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Composer stage to install PHP dependencies&lt;/li&gt;
&lt;li&gt;a Node stage to build frontend assets&lt;/li&gt;
&lt;li&gt;a final PHP-FPM image focused only on runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 The goal is simple: keep the runtime image as small, secure and predictable as possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Idea: Build-Time vs Runtime
&lt;/h2&gt;

&lt;p&gt;One of the biggest improvements comes from moving as much work as possible to build time.&lt;/p&gt;

&lt;p&gt;In my experience, this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installing dependencies during build (not at runtime)&lt;/li&gt;
&lt;li&gt;compiling assets once&lt;/li&gt;
&lt;li&gt;shipping only the final artifacts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 The container becomes immutable and easier to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example (Simplified)
&lt;/h2&gt;

&lt;p&gt;Instead of a single-stage Dockerfile, the idea is to split concerns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Composer stage (dependencies)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-dev&lt;/span&gt;

&lt;span class="c"&gt;# Node stage (assets)&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run build

&lt;span class="c"&gt;# Final image&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; built assets + vendor only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 This is intentionally simplified, but it reflects the core idea.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;With this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;builds are faster thanks to caching&lt;/li&gt;
&lt;li&gt;images are significantly smaller&lt;/li&gt;
&lt;li&gt;attack surface is reduced&lt;/li&gt;
&lt;li&gt;development and production concerns are clearly separated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In production environments, these differences quickly become critical.&lt;/p&gt;




&lt;h2&gt;
  
  
  Going Further
&lt;/h2&gt;

&lt;p&gt;This article only covers the Docker image itself.&lt;/p&gt;

&lt;p&gt;In a real production setup, you also need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a proper Docker Compose architecture&lt;/li&gt;
&lt;li&gt;a CI/CD pipeline to build and validate images&lt;/li&gt;
&lt;li&gt;deployment strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 I’m progressively documenting this production setup (Docker, Compose, CI/CD and more) here:&lt;br&gt;
&lt;a href="https://filamentmastery.com/articles/production-ready-docker-setup-for-laravel-filament/" rel="noopener noreferrer"&gt;https://filamentmastery.com/articles/production-ready-docker-setup-for-laravel-filament/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docker setups are often underestimated in Laravel projects.&lt;/p&gt;

&lt;p&gt;But in my experience, investing in a clean, production-ready setup early saves a lot of time later, especially when your application starts scaling.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>docker</category>
      <category>devops</category>
      <category>filament</category>
    </item>
    <item>
      <title>How I Structure My Filament Projects Today</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Wed, 08 Apr 2026 15:19:23 +0000</pubDate>
      <link>https://dev.to/filamentmastery/how-i-structure-my-filament-projects-today-2k4o</link>
      <guid>https://dev.to/filamentmastery/how-i-structure-my-filament-projects-today-2k4o</guid>
      <description>&lt;p&gt;I’ve been working with Laravel for several years now, and around 3 years with Filament. Today, most of my client projects use Filament for backend management, and sometimes for frontend components using Livewire.&lt;/p&gt;

&lt;p&gt;Over time, I’ve developed a simple and pragmatic way to structure my Filament projects. It’s not necessarily the best approach, but it’s the one that allows me to move fast and stay efficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting Point
&lt;/h2&gt;

&lt;p&gt;I almost always start from one of my two templates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://filamentmastery.com/articles/laravel-filament-backend-starter-build-your-admin-panel-fast/" rel="noopener noreferrer"&gt;&lt;strong&gt;Laravel Filament Backend Starter&lt;/strong&gt;&lt;/a&gt; (for simple backends)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://filamentmastery.com/articles/laravel-filament-multipanel-starter-build-your-app-fast/" rel="noopener noreferrer"&gt;&lt;strong&gt;Laravel Filament Multi Panel Starter&lt;/strong&gt;&lt;/a&gt; (when multiple panels are needed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m not covering multi-tenancy here, as I’ve only worked on one such project so far. It would be close to the multi-panel setup, but I haven’t industrialized it enough yet to publish it.&lt;/p&gt;

&lt;p&gt;These starters allow me to begin with a solid base that already includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication + optional 2FA&lt;/li&gt;
&lt;li&gt;Queue management with &lt;a href="https://laravel.com/docs/13.x/horizon?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Laravel Horizon&lt;/a&gt;, to easily monitor jobs from the backend&lt;/li&gt;
&lt;li&gt;Log management with &lt;a href="https://github.com/opcodesio/log-viewer?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Log Viewer&lt;/a&gt;, accessible from the backend&lt;/li&gt;
&lt;li&gt;Roles and permissions with &lt;a href="https://github.com/spatie/laravel-permission?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Spatie Laravel Permission&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Password expiration handling with my own package &lt;a href="https://filamentphp.com/plugins/yebor974-renew-password?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Filament Renew Password&lt;/a&gt;  (useful for projects in France where clients often require periodic password changes)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pestphp.com/?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Pest&lt;/a&gt; for unit and feature testing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/fruitcake/laravel-debugbar?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Laravel Debugbar&lt;/a&gt; for development&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/laravel/pint?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Pint&lt;/a&gt; for code style&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/phpstan/phpstan?ref=filamentmastery.com" rel="noopener noreferrer"&gt;PHPStan&lt;/a&gt; for static analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;For project structure, I stick to Filament’s default organization.&lt;/p&gt;

&lt;p&gt;I typically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a subfolder per panel&lt;/li&gt;
&lt;li&gt;add a &lt;code&gt;shared&lt;/code&gt; folder for reusable schemas if I have some panels with shared components (Tables, Forms, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I don’t always extract schemas into dedicated &lt;code&gt;shared&lt;/code&gt; folder. Sometimes I keep them directly inside resource folders and refactor later if needed.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Over-optimization can hurt efficiency 😄&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdz75y1lgbuf7k8kpbpxi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdz75y1lgbuf7k8kpbpxi.png" alt="How I Structure My Filament Projects Today" width="530" height="864"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Project structure example&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I work with PHPStorm, and usually host my code on my personal GitLab with a CI/CD pipeline (including SAST, secret detection, Composer &amp;amp; NPM audit, container scanning, etc.). I might share some of these resources in a future article.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0o8wvklj8x7bgfq0xz7k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0o8wvklj8x7bgfq0xz7k.png" alt="How I Structure My Filament Projects Today" width="800" height="150"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;One of my Gitlab CI Project&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Choices
&lt;/h2&gt;

&lt;p&gt;For larger projects with a more complex roadmap, I usually introduce a  &lt;strong&gt;Service layer&lt;/strong&gt;  to centralize business logic.&lt;/p&gt;

&lt;p&gt;I’ve experimented with more advanced patterns like Domain-Driven Design or hexagonal architecture. However, in most real-world projects I’ve worked on, these approaches added complexity without bringing significant long-term value.&lt;/p&gt;

&lt;p&gt;Today, I prefer to keep things simple and refactor when necessary.&lt;/p&gt;

&lt;p&gt;In some cases, I add a Repository layer on top of Services, but I feel it often reduces the power and simplicity of Eloquent while adding an unnecessary layer.&lt;/p&gt;

&lt;p&gt;😅&lt;/p&gt;

&lt;p&gt;This may not be the “best” way to do things, but it’s the one I use in most of my projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Tools
&lt;/h2&gt;

&lt;p&gt;With this setup, I already have a strong foundation that saves me a lot of time when starting a project.&lt;/p&gt;

&lt;p&gt;Depending on the needs, I then add specific packages such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://laravel-excel.com/?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Laravel Excel&lt;/a&gt; for advanced Excel exports, often combined with Filament’s export system&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://laravel.com/docs/13.x/socialite?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Laravel Socialite&lt;/a&gt; for authentication with client providers (SAML v2, Google, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/spatie/laravel-activitylog?ref=filamentmastery.com" rel="noopener noreferrer"&gt;Spatie Laravel Activity Log&lt;/a&gt; for user activity tracking, sometimes extended with timestamping and hashing for audit purposes&lt;/li&gt;
&lt;li&gt;Some filesystem libraries like &lt;a href="https://github.com/thephpleague/flysystem-sftp-v3?ref=filamentmastery.com" rel="noopener noreferrer"&gt;&lt;code&gt;league/flysystem-sftp-v3&lt;/code&gt;&lt;/a&gt; for SFTP file transfers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Over time, I’ve realized that what matters most is not having a perfect architecture from day one, but having a simple foundation that allows you to move forward quickly.&lt;/p&gt;

&lt;p&gt;Today, I tend to prioritize simplicity and efficiency, improving things along the way when needed.&lt;/p&gt;

&lt;p&gt;Feel free to let me know in the comments if this kind of article is useful. It’s a bit different from what I’ve shared so far on Filament Mastery.&lt;/p&gt;

&lt;p&gt;For more posts: &lt;a href="https://filamentmastery.com" rel="noopener noreferrer"&gt;Filament Mastery&lt;/a&gt;&lt;/p&gt;

</description>
      <category>filament</category>
      <category>laravel</category>
      <category>php</category>
      <category>devcommunity</category>
    </item>
    <item>
      <title>A fresh start with GHOST for Filament Mastery!</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Mon, 06 Apr 2026 14:59:39 +0000</pubDate>
      <link>https://dev.to/filamentmastery/a-fresh-start-with-ghost-for-filament-mastery-1kkf</link>
      <guid>https://dev.to/filamentmastery/a-fresh-start-with-ghost-for-filament-mastery-1kkf</guid>
      <description>&lt;p&gt;After several months of reflection, I’ve moved the site to a new platform.&lt;/p&gt;

&lt;p&gt;The goal is simple:  &lt;strong&gt;focus on what matters most, creating useful content&lt;/strong&gt; , including tutorials, practical insights, and actionable development resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s New
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The site now runs on a simpler platform, better suited for publishing content&lt;/li&gt;
&lt;li&gt;Reading experience is smoother and faster&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can now comment on posts!&lt;/strong&gt;  Your feedback and questions are welcome.&lt;/li&gt;
&lt;li&gt;I’ll be able to publish more regularly, with fewer technical constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Some old links may not work immediately but will be reactivated gradually.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Your Accounts
&lt;/h3&gt;

&lt;p&gt;Good news:&lt;br&gt;&lt;br&gt;
👉  &lt;strong&gt;Your accounts have been automatically migrated&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you were subscribed to the newsletter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You’ll continue to receive upcoming content&lt;/li&gt;
&lt;li&gt;No action is required on your part&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What’s Next
&lt;/h3&gt;

&lt;p&gt;I’ll be gradually publishing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Practical tutorials&lt;/li&gt;
&lt;li&gt;Hands-on guides&lt;/li&gt;
&lt;li&gt;Insights and lessons from my projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a clear goal:  &lt;strong&gt;sharing content that’s useful, actionable, and free of unnecessary fluff.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thank you all for your support 🙏&lt;br&gt;&lt;br&gt;
I can’t wait to read your comments and share the next pieces of content soon!&lt;/p&gt;

&lt;p&gt;📬 &lt;a href="https://filamentmastery.com" rel="noopener noreferrer"&gt;Join the community on filamentmastery.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blognews</category>
      <category>ghost</category>
      <category>filament</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Filament slow on large table? Optimize with Postgres partitions</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Tue, 13 Jan 2026 07:20:56 +0000</pubDate>
      <link>https://dev.to/filamentmastery/filament-slow-on-large-table-optimize-with-postgres-partitions-52l2</link>
      <guid>https://dev.to/filamentmastery/filament-slow-on-large-table-optimize-with-postgres-partitions-52l2</guid>
      <description>&lt;p&gt;When I started working on a client project, I ran into a familiar challenge: a Filament Resource managing millions of sensor measurements.&lt;br&gt;&lt;br&gt;
The table contained several years of historical data. Most users usually focused on the current month, but occasionally they needed to look back at older measurements for special cases.&lt;br&gt;&lt;br&gt;
At first, it looked like the resource was slow simply due to its sheer size, but profiling revealed a deeper issue. It became clear that Filament itself was not the bottleneck, the database was.&lt;/p&gt;
&lt;h2&gt;
  
  
  Identifying the problem
&lt;/h2&gt;

&lt;p&gt;Filtering by date or sensor type took several seconds, even for just the current month. Sorting and pagination felt sluggish. It was a classic case of “everything works, but not fast enough for production.”&lt;br&gt;&lt;br&gt;
The goal was simple: allow fast access to current data while keeping historical queries possible, without rewriting the panel.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The example below has been simplified for clarity. In the real project, additional filters and performance optimizations were applied. Default dates focus on the current month, but older data queries remain fully supported. The project was built with FilamentPHP and PostgreSQL.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Partitioning the table by month
&lt;/h2&gt;

&lt;p&gt;To solve the problem, I used PostgreSQL table partitioning, splitting the data by month. Here’s a simplified example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Parent table
CREATE TABLE sensor_measurements (
    id BIGSERIAL NOT NULL,
    sensor_id BIGINT NOT NULL REFERENCES sensors(id) ON DELETE CASCADE,
    value NUMERIC(10,2) NOT NULL,
    measured_at TIMESTAMP(0) NOT NULL,
      data JSONB
      created_at TIMESTAMP(0) DEFAULT now(),
    updated_at TIMESTAMP(0) DEFAULT now(),
      PRIMARY KEY (id, measured_at)
) PARTITION BY RANGE (measured_at);

// a month partition
CREATE TABLE IF NOT EXISTS sensor_measurements_2026_01 PARTITION OF sensor_measurements
    FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');

CREATE TABLE IF NOT EXISTS sensor_measurements_2026_02 PARTITION OF sensor_measurements
    FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The partition key need to be in the primary key.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Partitioning ensures queries for the current month only scan a small, relevant subset, keeping performance high. Historical queries still work efficiently by targeting older partitions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I created a scheduled job to automatically create new year/month partitions. More information about PostgreSQL partitioning is available in the &lt;a href="https://www.postgresql.org/docs/current/ddl-partitioning.html" rel="noopener noreferrer"&gt;official PostgreSQL documentation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Adding indexes and optimizing queries
&lt;/h2&gt;

&lt;p&gt;Indexes must be created on each partition, not on the parent table. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE INDEX idx_sensor_measurements_2026_01_data_gin
    ON sensor_measurements_2026_01(sensor_id)
      USING GIN (data);

CREATE INDEX idx_sensor_measurements_2026_01_measured_at 
    ON sensor_measurements_2026_02(measured_at)
      USING GIN (data);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that even with filters, the database can quickly find the relevant rows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adjusting the Filament Resource
&lt;/h2&gt;

&lt;p&gt;Finally, I adapted the Filament Resource with default filters for the current month:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Carbon\Carbon;

class SensorMeasurementResource extends Resource
{
      //...

    public static function table(Table $table): Table
    {
        $now = Carbon::now();

        return $table
            -&amp;gt;columns([
                TextColumn::make('sensor_id')-&amp;gt;sortable(),
                TextColumn::make('value'),
                TextColumn::make('measured_at')-&amp;gt;dateTime(),
            ])
            -&amp;gt;filters([
                Filter::make('date')
                    -&amp;gt;form([
                        DatePicker::make('from')
                            -&amp;gt;default($now-&amp;gt;copy()-&amp;gt;startOfMonth()),
                        DatePicker::make('to')
                            -&amp;gt;default($now-&amp;gt;copy()-&amp;gt;endOfMonth()),
                    ])
                    -&amp;gt;query(fn(Builder $query, array $data) =&amp;gt; $query
                        -&amp;gt;when($data['from'], fn($q) =&amp;gt; $q-&amp;gt;where('measured_at', '&amp;gt;=', $data['from']))
                        -&amp;gt;when($data['to'], fn($q) =&amp;gt; $q-&amp;gt;where('measured_at', '&amp;lt;=', $data['to']))
                    ),
            ]);
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;I didn't include the TableSchema file in this example for simplicity. One limitation is that the filter cannot be made mandatory without allowing users to remove it from the indicators toolbar. The goal is to always have a date range to optimize database loading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;The impact was immediate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queries for the current month now return in under 300ms&lt;/li&gt;
&lt;li&gt;Historical queries remain fast enough for occasional analysis&lt;/li&gt;
&lt;li&gt;Users can explore both current and past data without frustration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Filament can handle large datasets, if the underlying database is optimized.&lt;/li&gt;
&lt;li&gt;Partitioning by time periods is ideal for sensor or historical measurement data.&lt;/li&gt;
&lt;li&gt;Proper indexes + pagination make huge tables usable in production.&lt;/li&gt;
&lt;li&gt;Profiling queries first saves hours of wasted debugging in the admin panel.&lt;/li&gt;
&lt;li&gt;Small tweaks in Filament (filters, lazy-loading) can dramatically improve UX.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you enjoyed this kind of real-world experience and case study, don’t forget to like and share it!&lt;/p&gt;

&lt;p&gt;📬 &lt;a href="https://filamentmastery.com" rel="noopener noreferrer"&gt;Join the community on filamentmastery.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>filament</category>
      <category>laravel</category>
      <category>php</category>
      <category>postgres</category>
    </item>
    <item>
      <title>One year of Filament Mastery - A personal look back at 2025</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Wed, 31 Dec 2025 08:13:03 +0000</pubDate>
      <link>https://dev.to/filamentmastery/one-year-of-filament-mastery-a-personal-look-back-at-2025-1ek1</link>
      <guid>https://dev.to/filamentmastery/one-year-of-filament-mastery-a-personal-look-back-at-2025-1ek1</guid>
      <description>&lt;p&gt;About a year ago, I launched &lt;a href="https://filamentmastery.com" rel="noopener noreferrer"&gt;Filament Mastery&lt;/a&gt; with a pretty simple goal: create a place around FilamentPHP that I would genuinely enjoy reading myself.&lt;/p&gt;

&lt;p&gt;No hype, no endless tutorials rewritten ten times, no over-engineered examples, just clear, useful content for developers building things with Filament.&lt;/p&gt;

&lt;p&gt;As 2025 comes to an end, this felt like the right moment to pause for a bit and look back at what Filament Mastery has become over its first year.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filament Mastery in numbers
&lt;/h2&gt;

&lt;p&gt;I usually don’t obsess too much over metrics, but numbers can still tell part of the story.&lt;/p&gt;

&lt;p&gt;After one year, Filament Mastery looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;347 community members&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;274 newsletter subscribers&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;35 published articles&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;12 community links&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4 partners supporting the project&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They’re just numbers, but behind them are real people reading, learning, experimenting, and building things with Filament.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4gev7bl6lggr476j2uc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4gev7bl6lggr476j2uc.png" alt="Filament Mastery Dashboard 2025" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A community that goes beyond borders
&lt;/h2&gt;

&lt;p&gt;One thing that surprised me early on was how international the audience became.&lt;/p&gt;

&lt;p&gt;Looking at the dashboard, users are spread across many different time zones. Some of that data isn’t perfectly accurate (a lot of users are still grouped under UTC), but the overall trend is clear:&lt;br&gt;&lt;br&gt;
Filament Mastery is being read all over the world.&lt;/p&gt;

&lt;p&gt;Different countries, different projects, different levels of experience — but the same interest in building solid back-offices with Filament.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this year taught me
&lt;/h2&gt;

&lt;p&gt;Working on Filament Mastery over the past year reinforced a few things I already suspected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers value clarity more than complexity.&lt;/li&gt;
&lt;li&gt;Not every problem needs a clever abstraction or a custom solution.&lt;/li&gt;
&lt;li&gt;Filament works best when you lean into its conventions instead of fighting them.&lt;/li&gt;
&lt;li&gt;Writing content that &lt;em&gt;you&lt;/em&gt; would actually use is usually the right approach.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I intentionally kept Filament Mastery focused and calm. No rush to publish, no pressure to cover everything, just content that feels useful and honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  A sincere thank you
&lt;/h2&gt;

&lt;p&gt;Even though I’m writing this from a personal perspective, Filament Mastery wouldn’t exist without the people reading it and writing.&lt;/p&gt;

&lt;p&gt;So thank you, whether you’ve read one article, subscribed to the newsletter, shared a link, wrote an article or simply bookmarked the site and come back from time to time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to follow Filament Mastery
&lt;/h2&gt;

&lt;p&gt;If you’d like to stay updated, share content, or help the community grow, you can also find Filament Mastery on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://x.com/FilamentMastery" rel="noopener noreferrer"&gt;X.com&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://dev.to/filamentmastery"&gt;Dev.to&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.facebook.com/profile.php?id=61569380882543" rel="noopener noreferrer"&gt;Facebook&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://www.linkedin.com/company/filament-mastery" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following and sharing helps more than you might think.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking ahead to 2026
&lt;/h2&gt;

&lt;p&gt;I don’t have a big roadmap or ambitious promises for 2026 — and that’s very much on purpose.&lt;/p&gt;

&lt;p&gt;Most of what I share on Filament Mastery comes directly from real client projects.&lt;br&gt;&lt;br&gt;
Things I actually use, patterns that work in practice, and decisions made under real-world constraints. I usually avoid diving too deep into heavy concepts like strict DDD or over-abstracted architectures, not because they’re wrong, but because they often add complexity and make articles harder to read and reuse.&lt;/p&gt;

&lt;p&gt;My goal is to keep sharing practical ideas that you can adapt easily, without forcing a specific methodology or way of thinking.&lt;/p&gt;

&lt;p&gt;I also want Filament Mastery to stay open and community-driven.&lt;br&gt;&lt;br&gt;
Anyone can write and publish content directly from their member area, and this will remain &lt;strong&gt;free&lt;/strong&gt;. No paywalls, no hidden requirements, just useful content shared by people building real things.&lt;/p&gt;

&lt;p&gt;On a more personal note, 2026 will also be a bit different for me. I’m planning to spend around six months in Europe, and if the opportunity comes up, I’ll try to attend local meetups or events. Nothing official or planned yet, just a chance to meet people from the community in real life.&lt;/p&gt;

&lt;p&gt;For now, the direction stays simple: build, learn, share and keep things approachable.&lt;/p&gt;

&lt;p&gt;Thanks for being part of this first year, and see you in 2026 🚀&lt;/p&gt;

&lt;p&gt;📬 &lt;a href="https://filamentmastery.com" rel="noopener noreferrer"&gt;Join the community on filamentmastery.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>filament</category>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
