<?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.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>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>
    <item>
      <title>How to send Filament database notifications to a specific queue</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Thu, 04 Sep 2025 10:10:16 +0000</pubDate>
      <link>https://dev.to/filamentmastery/how-to-send-filament-database-notifications-to-a-specific-queue-1e91</link>
      <guid>https://dev.to/filamentmastery/how-to-send-filament-database-notifications-to-a-specific-queue-1e91</guid>
      <description>&lt;p&gt;When working with &lt;code&gt;Filament\Notifications\Notification&lt;/code&gt;, sending database notifications with &lt;code&gt;sendToDatabase()&lt;/code&gt; is super convenient. But what if you want to control which queue these notifications are dispatched to?&lt;/p&gt;

&lt;p&gt;At first glance, this looks tricky, Filament doesn’t expose a queue configuration option directly. But the good news is: you can still take full advantage of Laravel’s queue system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default behavior for Filament notifications on database
&lt;/h2&gt;

&lt;p&gt;The usual Filament way looks like this:&lt;br&gt;
&lt;/p&gt;

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

Notification::make()
    -&amp;gt;title("Your request has been processed")
    -&amp;gt;body("Some details about the request")
    -&amp;gt;sendToDatabase($user);

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

&lt;/div&gt;



&lt;p&gt;What happens under the hood:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filament calls &lt;code&gt;$user-&amp;gt;notify($this-&amp;gt;toDatabase())&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;toDatabase()&lt;/code&gt; creates a &lt;code&gt;Filament\Notifications\DatabaseNotification&lt;/code&gt;, which extends Laravel’s &lt;code&gt;Notification&lt;/code&gt;, implements &lt;code&gt;ShouldQueue&lt;/code&gt;, and uses the &lt;code&gt;Queueable&lt;/code&gt; trait.&lt;/li&gt;
&lt;li&gt;That means the notification goes into the queue defined by your &lt;code&gt;QUEUE_CONNECTION&lt;/code&gt; (&lt;code&gt;database&lt;/code&gt;, &lt;code&gt;redis&lt;/code&gt;, etc.), but you &lt;strong&gt;cannot specify which queue name&lt;/strong&gt; this way.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to control the queue: Using &lt;code&gt;toDatabase()&lt;/code&gt; + &lt;code&gt;onQueue()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Instead of &lt;code&gt;sendToDatabase()&lt;/code&gt;, grab the notification instance with &lt;code&gt;toDatabase()&lt;/code&gt; and apply &lt;code&gt;onQueue()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use Filament\Notifications\Notification as FilamentNotification;

$databaseNotification = FilamentNotification::make()
    -&amp;gt;title("Your request has been processed")
    -&amp;gt;body("Some details about the request")
    -&amp;gt;toDatabase() // returns a DatabaseNotification (Laravel Notification)
    -&amp;gt;onQueue('notifications'); // from Queueable trait

$user-&amp;gt;notify($databaseNotification);

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

&lt;/div&gt;



&lt;p&gt;Or, in a single line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$user-&amp;gt;notify(
    FilamentNotification::make()
        -&amp;gt;title("Your request has been processed")
        -&amp;gt;body("Some details about the request")
        -&amp;gt;toDatabase()
        -&amp;gt;onQueue('notifications')
);

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

&lt;/div&gt;



&lt;p&gt;This way you keep the simplicity of Filament’s fluent API &lt;strong&gt;and&lt;/strong&gt; you get fine-grained queue control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multiple users example
&lt;/h2&gt;

&lt;p&gt;If you need to notify multiple users on the same queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$notif = FilamentNotification::make()
    -&amp;gt;title("Your request has been processed")
    -&amp;gt;body("Some details about the request")
    -&amp;gt;toDatabase()
    -&amp;gt;onQueue('notifications');

foreach ($users as $user) {
    $user-&amp;gt;notify($notif); // reuses the same notification instance
}

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

&lt;/div&gt;



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

&lt;p&gt;If you just want to send database notifications, Filament’s &lt;code&gt;sendToDatabase()&lt;/code&gt; is perfect.&lt;/p&gt;

&lt;p&gt;But if you need &lt;strong&gt;queue control&lt;/strong&gt; , switch to &lt;code&gt;toDatabase()-&amp;gt;onQueue('...')&lt;/code&gt;. That way you stay fully in the Filament ecosystem, so your notifications are correctly stored and displayed in your Filament panels, while still taking advantage of Laravel’s queue flexibility.&lt;/p&gt;

&lt;p&gt;🙏 Thanks to &lt;a href="https://github.com/obaume" rel="noopener noreferrer"&gt;@obaume&lt;/a&gt; for reaching out on this topic. His question made me dive deeper into Filament notifications and discover this approach!&lt;/p&gt;

&lt;p&gt;💡 Community Idea: Currently, &lt;code&gt;sendToDatabase()&lt;/code&gt; doesn’t allow specifying a queue. If this article gets enough likes or shares, I’ll consider proposing a Pull Request to Filament to add this feature—making queue management even easier for everyone!&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; — it's free!&lt;/p&gt;

</description>
      <category>filament</category>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Filament Email Verification: Leveraging Laravel’s Event System</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Wed, 20 Aug 2025 04:11:50 +0000</pubDate>
      <link>https://dev.to/filamentmastery/filament-email-verification-leveraging-laravels-event-system-2dmm</link>
      <guid>https://dev.to/filamentmastery/filament-email-verification-leveraging-laravels-event-system-2dmm</guid>
      <description>&lt;p&gt;In this tutorial, we'll explore how to trigger an event when a user's email is verified in a Filament application. While this is primarily a Laravel concept, Filament fully supports and integrates with Laravel's powerful event and listener system. You'll see how to apply this to Filament projects seamlessly.&lt;/p&gt;

&lt;p&gt;We’ll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a listener for the email verification event,&lt;/li&gt;
&lt;li&gt;Triggering actions such as Stripe registration or newsletter subscription,&lt;/li&gt;
&lt;li&gt;Testing the event with &lt;code&gt;Event::fake&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;Extending the logic to other events like user registration or login.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end, you'll understand how to leverage Laravel's event system in your FilamentPHP apps for advanced user workflows.&lt;/p&gt;

&lt;p&gt;To set up your project for enabling email verification on panel, you can check the first step of &lt;a href="https://filamentmastery.com/articles/email-verification-in-filament-userresource-filters-and-actions" rel="noopener noreferrer"&gt;this article&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create a Listener for the &lt;code&gt;Verified&lt;/code&gt; Event
&lt;/h2&gt;

&lt;p&gt;Laravel emits the &lt;strong&gt;&lt;code&gt;Illuminate\Auth\Events\Verified&lt;/code&gt;&lt;/strong&gt; event when a user successfully verifies their email. Here's how to hook into it.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.1 Generate the Listener
&lt;/h3&gt;

&lt;p&gt;Run the following command to create a new listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:listener HandleUserEmailVerified

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

&lt;/div&gt;



&lt;p&gt;This command will create a file in &lt;code&gt;App\Listeners&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Listeners;

use Illuminate\Auth\Events\Verified;

class HandleUserEmailVerified
{
    /**
     * Handle the event.
     *
     * @param \Illuminate\Auth\Events\Verified $event
     * @return void
     */
    public function handle(Verified $event)
    {
        ///
    }
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  1.2 Implement the Listener
&lt;/h3&gt;

&lt;p&gt;Update the listener to perform actions after email verification, 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;namespace App\Listeners;

use Illuminate\Auth\Events\Verified;

class HandleUserEmailVerified
{
    /**
     * Handle the event.
     *
     * @param \Illuminate\Auth\Events\Verified $event
     * @return void
     */
    public function handle(Verified $event)
    {
        $user = $event-&amp;gt;user;

        // Example: Create a Stripe account
        if (!$user-&amp;gt;stripe_id) {
            $this-&amp;gt;createStripeAccount($user);
        }

        // Example: Subscribe to a newsletter
        $this-&amp;gt;subscribeToNewsletter($user);
    }

    protected function createStripeAccount($user)
    {
        // Logic to integrate with Stripe API
    }

    protected function subscribeToNewsletter($user)
    {
        // Logic to integrate with a mailing list API
    }
}

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Register the Listener for the &lt;code&gt;Verified&lt;/code&gt; Event
&lt;/h2&gt;

&lt;p&gt;If you're using Laravel 10 or later, Laravel can auto-discover event-listener mappings. So this step may not always be required, but keeping it explicit helps with debugging.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Providers;

use Illuminate\Auth\Events\Verified;
use App\Listeners\HandleUserEmailVerified;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        Verified::class =&amp;gt; [
            HandleUserEmailVerified::class,
        ],
    ];
}

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Test the Listener with &lt;code&gt;Event::fake&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Before using this in production, ensure everything works as expected. Laravel provides a helpful testing utility to fake events and assert their behavior.&lt;/p&gt;

&lt;p&gt;Here’s a test example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace Tests\Feature;

use Illuminate\Auth\Events\Verified;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Tests\TestCase;
use App\Models\User;

class EmailVerificationTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function it_dispatches_verified_event_after_email_verification()
    {
        Event::fake();

        $user = User::factory()-&amp;gt;unverified()-&amp;gt;create();

        $user-&amp;gt;markEmailAsVerified();

        // Assert the Verified event was dispatched
        Event::assertDispatched(Verified::class, function ($event) use ($user) {
            return $event-&amp;gt;user-&amp;gt;is($user);
        });
    }
}

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4: Applying the concept in FilamentPHP
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why This Matters in Filament
&lt;/h3&gt;

&lt;p&gt;Filament is built on Laravel, meaning you can use Laravel's event system in your Filament-powered applications. For example, when managing a &lt;strong&gt;User Resource&lt;/strong&gt; , you can leverage the same listener to perform actions after email verification.&lt;/p&gt;

&lt;p&gt;You could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add custom hooks in Filament tables or forms,&lt;/li&gt;
&lt;li&gt;Use Filament notifications to display messages when an event is triggered,&lt;/li&gt;
&lt;li&gt;Extend user workflows directly within the Filament admin panel.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: Extending the Principle
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Event: &lt;code&gt;Registered&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;&lt;code&gt;Illuminate\Auth\Events\Registered&lt;/code&gt;&lt;/strong&gt; event fires after a new user is created. This is useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sending a welcome email,&lt;/li&gt;
&lt;li&gt;Assigning default roles,&lt;/li&gt;
&lt;li&gt;Creating a related profile or team entry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Listeners;

use Illuminate\Auth\Events\Registered;

class HandleUserRegistered
{
    public function handle(Registered $event)
    {
        $user = $event-&amp;gt;user;

        // Assign a default role
        $user-&amp;gt;assignRole('default');
    }
}

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Event: &lt;code&gt;Login&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;&lt;code&gt;Illuminate\Auth\Events\Login&lt;/code&gt;&lt;/strong&gt; event fires upon user login. Use it to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log user activity,&lt;/li&gt;
&lt;li&gt;Update a "last login" timestamp,&lt;/li&gt;
&lt;li&gt;Sync data with an external system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace App\Listeners;

use Illuminate\Auth\Events\Login;

class HandleUserLogin
{
    public function handle(Login $event)
    {
        $user = $event-&amp;gt;user;

        // Update last login time
        $user-&amp;gt;update(['last_login_at' =&amp;gt; now()]);
    }
}

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

&lt;/div&gt;



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

&lt;p&gt;By using Laravel's event system into your Filament projects, you can create robust workflows tailored to user actions. Whether it's email verification, registration, or login, the possibilities are endless.&lt;/p&gt;

&lt;p&gt;📬 &lt;a href="https://filamentmastery.com/member/register" rel="noopener noreferrer"&gt;Join the community on filamentmastery.com&lt;/a&gt; — it's free!&lt;/p&gt;

</description>
      <category>filament</category>
      <category>laravel</category>
      <category>php</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Laravel Filament Multipanel Starter - Build your app fast</title>
      <dc:creator>yebor974</dc:creator>
      <pubDate>Wed, 13 Aug 2025 11:20:37 +0000</pubDate>
      <link>https://dev.to/filamentmastery/laravel-filament-multipanel-starter-build-your-app-fast-9i3</link>
      <guid>https://dev.to/filamentmastery/laravel-filament-multipanel-starter-build-your-app-fast-9i3</guid>
      <description>&lt;p&gt;Looking for a &lt;strong&gt;ready-to-use admin and member panels&lt;/strong&gt; for your Laravel projects? Meet &lt;strong&gt;Laravel Filament Multipanel Starter&lt;/strong&gt; — a clean, opinionated starter kit designed to help you &lt;strong&gt;ship faster&lt;/strong&gt; with &lt;strong&gt;Laravel 12&lt;/strong&gt; and &lt;strong&gt;Filament PHP v4&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s included?
&lt;/h2&gt;

&lt;p&gt;Setting up a secure and well-structured backend can take hours. With this starter, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dedicated &lt;strong&gt;admin panel&lt;/strong&gt; accessible via path or subdomain&lt;/li&gt;
&lt;li&gt;Dedicated &lt;strong&gt;member panel&lt;/strong&gt; accessible via path or subdomain&lt;/li&gt;
&lt;li&gt;Secure authentication with:

&lt;ul&gt;
&lt;li&gt;Email invitation system on user creation&lt;/li&gt;
&lt;li&gt;Password renewal required on first login (via &lt;a href="https://github.com/yebor974/filament-renew-password" rel="noopener noreferrer"&gt;Filament Renew Password&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Multi-factor authentication with App authentication (not required)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;User management (Filament Resource) with:

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/spatie/laravel-permission" rel="noopener noreferrer"&gt;Spatie Laravel Permission&lt;/a&gt; for roles &amp;amp; permissions&lt;/li&gt;
&lt;li&gt;Default roles and policies pre-seeded&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;User profile management (email, name, password, timezone)&lt;/li&gt;

&lt;li&gt;Light &amp;amp; Dark theme switch + empty custom theme ready to be extended&lt;/li&gt;

&lt;li&gt;Database notifications (Filament native support)&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://github.com/opcodesio/log-viewer" rel="noopener noreferrer"&gt;Logs Viewer&lt;/a&gt; integration (&lt;code&gt;opcodesio/log-viewer&lt;/code&gt;) — protected by auth &amp;amp; backend role&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://laravel.com/docs/horizon" rel="noopener noreferrer"&gt;Laravel Horizon&lt;/a&gt; — queued jobs management — protected by auth and backend role&lt;/li&gt;

&lt;li&gt;Timezone management from user profile - Custom registration for member panel with timezone auto-detect.&lt;/li&gt;

&lt;li&gt;Centralized date display settings (&lt;code&gt;TextColumn&lt;/code&gt;, &lt;code&gt;DateTimePicker&lt;/code&gt;)&lt;/li&gt;

&lt;li&gt;Default password policy (min 12 characters, mixed case, numbers, symbols)&lt;/li&gt;

&lt;li&gt;Fully &lt;strong&gt;localized&lt;/strong&gt; , default to 🇬🇧 english. Extends with your own languages&lt;/li&gt;

&lt;li&gt;Dev-friendly tools:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/barryvdh/laravel-debugbar" rel="noopener noreferrer"&gt;Laravel Debugbar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://phpstan.org/" rel="noopener noreferrer"&gt;PHPStan&lt;/a&gt; for static analysis&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://laravel.com/docs/pint" rel="noopener noreferrer"&gt;Pint&lt;/a&gt; for consistent code formatting&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Instead of reinventing the wheel every time you need a &lt;strong&gt;secure backend panel&lt;/strong&gt; and a &lt;strong&gt;secure member panel&lt;/strong&gt; , this template gives you a &lt;strong&gt;solid, scalable foundation&lt;/strong&gt; —with the best practices already in place.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Laravel 12&lt;/li&gt;
&lt;li&gt;Filament PHP v4&lt;/li&gt;
&lt;li&gt;Tailwind CSS v4&lt;/li&gt;
&lt;li&gt;PHP 8.2+&lt;/li&gt;
&lt;li&gt;PostgreSQL / MySQL&lt;/li&gt;
&lt;li&gt;Redis &amp;amp; Laravel Horizon&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;✅ SaaS admin and member panels&lt;/li&gt;
&lt;li&gt;✅ Internal dashboards&lt;/li&gt;
&lt;li&gt;✅ Intended for public-facing user portals (for a simple backend panel without member panel you can see &lt;a href="https://filamentmastery.com/articles/laravel-filament-backend-starter-build-your-admin-panel-fast" rel="noopener noreferrer"&gt;Laravel Filament Backend Starter&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Compare Starter Templates
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / Module&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;&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;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;&lt;a href="https://filamentmastery.com/articles/laravel-filament-multipanel-starter-build-your-app-fast" rel="noopener noreferrer"&gt;Multipanel Starter&lt;/a&gt;&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Laravel version&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Laravel 12&lt;/td&gt;
&lt;td&gt;Laravel 12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Filament version&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;v4&lt;/td&gt;
&lt;td&gt;v4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Backend Panel&lt;/strong&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;&lt;strong&gt;Member Panel (User Portal)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ User panel with login, registration, profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Panel-Specific Middleware &amp;amp; Routes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ Per panel config &amp;amp; middleware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Subdomain or Path Routing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ &lt;em&gt;(separate for each panel)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authentication via Email Invitation&lt;/strong&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;&lt;strong&gt;Password Reset on First Login&lt;/strong&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;&lt;strong&gt;Multi-factor authentication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅ (App authentication)&lt;/td&gt;
&lt;td&gt;✅ (App authentication)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Roles &amp;amp; Permissions (Spatie)&lt;/strong&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;&lt;strong&gt;Timezone Management&lt;/strong&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;&lt;strong&gt;User Profile Management&lt;/strong&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;&lt;strong&gt;Logs Viewer (Opcodes)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ &lt;em&gt;(protected backend user)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Laravel Horizon&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅ &lt;em&gt;(protected backend user)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dark Mode &amp;amp; Theme Ready&lt;/strong&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;&lt;strong&gt;Multi-language Support&lt;/strong&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;&lt;strong&gt;Dev Tools (Debugbar, PHPStan, Pint)&lt;/strong&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;&lt;strong&gt;Ready for SaaS Admin Panel&lt;/strong&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;&lt;strong&gt;Ready for Public-Facing User Portal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ &lt;em&gt;(ideal for memberships)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Use Case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Internal tools, dashboard.&lt;/td&gt;
&lt;td&gt;Full-stack app (admin + users)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Get it now
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://buy.stripe.com/cNibJ16Ardkb3ZOfNQ3wQ02" rel="noopener noreferrer"&gt;👉 Get Laravel Filament Multipanel Template&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://filamentmastery.com/member" rel="noopener noreferrer"&gt;Register or connect&lt;/a&gt; to your panel and get 10% off from the &lt;a href="https://filamentmastery.com/member/offers?tableFilters%5Bpartner_id%5D%5Bvalue%5D=3" rel="noopener noreferrer"&gt;offers page&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;You will receive your ZIP template by mail. Your purchase grants you the right to use this starter template on unlimited personal or client projects. Reselling or redistributing the code as a standalone product is not allowed.&lt;/em&gt;&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%2Fw99jji7ihpepv91yqvs2.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%2Fw99jji7ihpepv91yqvs2.png" alt="Laravel Filament Backend Login Page – Secure Access" width="800" height="458"&gt;&lt;/a&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%2Fy59f01k3zkqo3h7yytbe.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%2Fy59f01k3zkqo3h7yytbe.png" alt="Laravel Filament Backend Dashboard Overview" width="800" height="458"&gt;&lt;/a&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%2Fnjfy8sh5bajvd46vdrtl.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%2Fnjfy8sh5bajvd46vdrtl.png" alt="Members Management List in Laravel Filament" width="800" height="458"&gt;&lt;/a&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%2F8fpmucfscs4hr3gwx5km.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%2F8fpmucfscs4hr3gwx5km.png" alt="Members Management Edit in Laravel Filament" width="800" height="458"&gt;&lt;/a&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%2Fktkg4etn7nsspswr8icv.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%2Fktkg4etn7nsspswr8icv.png" alt="Laravel Filament Member Panel Profile" width="800" height="458"&gt;&lt;/a&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%2Fsbgnjer0uwam79x1dabk.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%2Fsbgnjer0uwam79x1dabk.png" alt="Laravel Filament Member Dashboard Overview" width="800" height="458"&gt;&lt;/a&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%2Fitd9qz3phkjifbmgwc80.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%2Fitd9qz3phkjifbmgwc80.png" alt="Laravel Filament Member Panel Registration" width="800" height="458"&gt;&lt;/a&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%2Fedohtum40ifxb7o25sa8.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%2Fedohtum40ifxb7o25sa8.png" alt="Laravel Filament Multi Panel Multi auth" width="800" height="458"&gt;&lt;/a&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%2F6aqpo78jozfzhcbrghkc.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%2F6aqpo78jozfzhcbrghkc.png" alt="Multi-factor authentication with Recovery codes in filament panel" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next?
&lt;/h2&gt;

&lt;p&gt;I'm working on &lt;strong&gt;more Filament templates and plugins&lt;/strong&gt; to make your life easier.&lt;a href="https://filamentmastery.com/member" rel="noopener noreferrer"&gt;Register to Filament Mastery&lt;/a&gt; for updates and new releases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://buy.stripe.com/cNibJ16Ardkb3ZOfNQ3wQ02" rel="noopener noreferrer"&gt;👉 Get Laravel Filament Multipanel Template&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Like this article if you'd like me to share more starter kits!&lt;/strong&gt;&lt;/p&gt;

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