惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
月光博客
月光博客
aimingoo的专栏
aimingoo的专栏
Google DeepMind News
Google DeepMind News
Recent Announcements
Recent Announcements
MyScale Blog
MyScale Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
SegmentFault 最新的问题
Hugging Face - Blog
Hugging Face - Blog
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
F
Fortinet All Blogs
小众软件
小众软件
D
Docker
U
Unit 42
博客园 - 聂微东
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
IT之家
IT之家
云风的 BLOG
云风的 BLOG
博客园 - 司徒正美
有赞技术团队
有赞技术团队
腾讯CDC

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Improve Filament Import UX with Persistent Error CSV Down...
yebor974 · 2026-05-11 · via DEV Community

By default, FilamentPHP provides a convenient import action with built-in feedback once the process is complete.

After an import finishes, a notification is displayed showing the result, along with a link to download a CSV file containing failed rows.

While this works well for simple use cases, it quickly becomes limiting in real-world applications:

  • Only the user who initiated the import can access the error file
  • The download link disappears once the notification is dismissed
  • There is no built-in way to view past imports or retry analysis

👉 In a team environment, this can become frustrating very quickly.

In this article, we'll implement a simple and clean solution to:

  • List all imports in a Filament resource
  • Allow authorized users to download error CSV files
  • Manage and delete past imports

Understanding Filament's Default Behavior

Filament already provides an internal model to handle imports:

Filament\Actions\Imports\Models\Import

Enter fullscreen mode Exit fullscreen mode

This model includes several useful attributes:

  • completed_at → timestamp
  • processed_rows → integer
  • total_rows → integer
  • successful_rows → integer

It also exposes computed values like:

$import->getFailedRowsCount()

Enter fullscreen mode Exit fullscreen mode

Filament also defines an internal route used to download failed rows:

Route::get('/imports/{import}/failed-rows/download', DownloadImportFailureCsv::class)
    ->name('imports.failed-rows.download');

Enter fullscreen mode Exit fullscreen mode

You don't need to override this route, we'll simply control access to it.

The Access Problem

By default, if no Policy is defined, Filament restricts access like this:

$importPolicy = Gate::getPolicyFor($import::class);

if (filled($importPolicy) && method_exists($importPolicy, 'view')) {
    Gate::forUser($user)->authorize('view', Arr::wrap($import));
} else {
    abort_unless($import->user()->is($user), 403);
}

Enter fullscreen mode Exit fullscreen mode

Extract of Filament\Actions\Imports\Http\Controllers\DownloadImportFailureCsv Controller

This means:

  • If no policy exists → only the owner can download the file
  • If a policy exists → Filament defers authorization to it

So the solution is simple: define a Policy.

Creating the Import Policy

Generate the policy:

php artisan make:policy ImportPolicy --model="Filament\Actions\Imports\Models\Import"

Enter fullscreen mode Exit fullscreen mode

Here is an example implementation:

namespace App\Policies;

use App\Enums\ImportImporterEnum;
use App\Enums\PermissionEnum;
use App\Models\User;
use App\Services\PermissionService;
use Filament\Actions\Imports\Models\Import;
use Illuminate\Auth\Access\HandlesAuthorization;

class ImportPolicy
{
    use HandlesAuthorization;

    public function __construct(protected PermissionService $permissionService) {}

    public function viewAny(User $user): bool
    {
        return $user->hasPermissionTo($this->permissionService->getPermissionName(PermissionEnum::VIEW_ANY, Import::class));
    }

    public function view(User $user, Import $import): bool
    {
        if (is_null($import->completed_at) || $import->getFailedRowsCount() == 0) {
            return false;
        }

        return $user->hasPermissionTo($this->permissionService->getPermissionName(PermissionEnum::VIEW, Import::class));
    }

    public function delete(User $user, Import $import): bool
    {
        if ($import->importer == ImportImporterEnum::PRODUCT_MAPPING->value) {
            return false;
        }

        return $user->hasPermissionTo($this->permissionService->getPermissionName(PermissionEnum::DELETE, Import::class));
    }
}

Enter fullscreen mode Exit fullscreen mode

In this example, permissions are handled using a role/permission system (e.g. Spatie Laravel Permission) with a own Permission Service and Enum. Adapt this logic to your own application.

Ideally, Filament would use a dedicated download method instead of view, as these represent different concerns. However, we'll stick with the default behavior for simplicity.

Registering the Policy

Don't forget to register the policy in your service provider:

use Filament\Actions\Imports\Models\Import;

public function boot(): void
{
    Gate::policy(Import::class, ImportPolicy::class);
    ...
}

Enter fullscreen mode Exit fullscreen mode

Creating the Filament Resource

Now let’s expose imports in the admin panel:

php artisan make:filament-resource Import --model-namespace=Filament\\Actions\\Imports\\Models

Enter fullscreen mode Exit fullscreen mode

We don’t need forms here, only a listing page. So you can delete Schemas format and some pages like CreateImport and EditImport

Improve Filament Import UX with Persistent Error CSV Downloads

Resource class

class ImportResource extends Resource
{
    protected static ?string $model = Import::class;

    public static function table(Table $table): Table
    {
        return ImportsTable::configure($table);
    }

    public static function getPages(): array
    {
        return [
            'index' => ListImports::route('/'),
        ];
    }
}

Enter fullscreen mode Exit fullscreen mode

Simplified Import Resource

Building the Imports Table

Here's a simplified version of the table:

class ImportsTable
{
    public static function configure(Table $table): Table
    {
        return $table
            ->columns([
                TextColumn::make('file_name')->searchable(),
                TextColumn::make('importer'),
                TextColumn::make('user.name'),
                TextColumn::make('total_rows')->numeric(),
                TextColumn::make('processed_rows')->numeric(),
                TextColumn::make('successful_rows')->numeric(),
                TextColumn::make('completed_at')->sortable(),
            ])
            ->recordActions([
                ActionGroup::make([
                    Action::make('downloadFailedRowsCsv')
                        ->label('Download errors')
                        ->color('warning')
                        ->icon(Heroicon::ArrowDownCircle)
                        ->url(fn (Import $import) => URL::signedRoute('filament.imports.failed-rows.download', ['authGuard' => filament()->getAuthGuard(), 'import' => $import], absolute: false), shouldOpenInNewTab: true)
                        ->authorize('view'),

                    DeleteAction::make(),
                ]),
            ])
            ->defaultSort('created_at', 'desc');
    }
}

Enter fullscreen mode Exit fullscreen mode

Simplified ImportsTable

Key Feature: Downloading Failed Rows

The most important part is this action:

Action::make('downloadFailedRowsCsv')

Enter fullscreen mode Exit fullscreen mode

It:

  • Uses Filament's internal signed route
  • Relies on the view policy for authorization

👉 Combined with the policy, this ensures:

  • Only authorized users can access error files
  • Downloads remain secure
  • UX is significantly improved

Final Result

With just a Policy and a Resource, you now have:

  • A complete history of imports
  • Persistent access to error CSV files
  • Team-friendly access control
  • A cleaner and more professional admin experience

Improve Filament Import UX with Persistent Error CSV Downloads
Import's actions with failed rows

Improve Filament Import UX with Persistent Error CSV Downloads
Import's actions without failed rows

Conclusion

Filament provides powerful building blocks, but some features, like import history, require a bit of customization to fully shine.

With this approach, you enhance both usability and maintainability without adding unnecessary complexity.

From here, you could go further by adding:

  • Retry mechanisms
  • Import status filters
  • etc.

🚀 Want to go further?

More articles on Filament Mastery

I’m sharing production-ready Laravel & Filament setups too (Docker, CI/CD, deployment…).
Join the Early Supporters tier for full access