Back to Blog
3 min read

Supercharging Admin Workflows: Bulk Status Updates and UI Polish in Filament PHP

The Admin Panel Bottleneck

Managing form submissions at scale in AustinsElite used to be a grind. Every time we needed to update the status of multiple entries—say, moving a batch from 'pending' to 'processed'—it meant clicking through each record individually in the Filament admin panel. Not only was it tedious, but it also introduced room for human error and slowed down operations during high-volume periods.

We knew we needed automation, but we also couldn’t compromise on safety. We didn’t want a blunt instrument—just a smart, controlled way to apply status changes across selected submissions. The solution? A custom Artisan command wrapped in validation guardrails, paired with targeted UI tweaks to make bulk actions easier to track.

Building a Safe Bulk Status Command

Our first move was to build a new Artisan command: php artisan submissions:bulk-status. It accepts a list of submission IDs, a target status, and an optional reason. But we didn’t stop at basic functionality—we baked in safeguards.

Before any update, the command checks:

  • Whether the current user has permission to modify each submission
  • If the target status is valid and allowed from the current state (respecting our internal status flow)
  • Whether the submission is locked or already archived

Here’s a simplified version of the logic:

foreach ($submissionIds as $id) {
    $submission = Submission::find($id);
    
    $this->validateUserAccess($user, $submission);
    $this->validateStatusTransition($submission->status, $newStatus);
    
    $submission->update(['status' => $newStatus]);
    
    // Log the change for audit trail
    StatusChangeLog::create([
        'submission_id' => $id,
        'old_status' => $submission->status,
        'new_status' => $newStatus,
        'changed_by' => $user->id,
        'reason' => $reason
    ]);
}

This command is now part of our standard ops toolkit. We can run it from the terminal during migrations, or wrap it in a queued job triggered from the Filament interface later down the line. It’s fast, auditable, and eliminates repetitive clicking.

Polishing the Filament Table for Real-World Use

With the backend in place, we turned to the frontend. The Filament table was functional but cluttered. Column widths were inconsistent, long text overflows made scanning hard, and—worst of all—the global search had accidentally been disabled during a recent refactor.

We made three key improvements:

  1. Restored global search — It sounds small, but losing search in an admin panel is like flying blind. We re-enabled it with a single line: ->searchable(), but also scoped it to key fields like reference_number, client_name, and notes to keep results relevant.

  2. Trimmed and formatted text columns — Long status notes were breaking table layout. We added ->limit(60) to text columns and used ->badge() with color coding for status fields. Now, at a glance, you can see which submissions are urgent, pending, or completed.

  3. Controlled feature visibility via config — Not every admin needs access to bulk actions. We introduced a config flag (admin.features.bulk_update) that toggles the visibility of the command interface in the panel. This lets us enable it for senior staff while keeping the UI clean for others.

use function config;

// In Filament Action
->hidden(fn () => ! config('admin.features.bulk_update'))

This approach gives us flexibility without branching codebases or roles.

The result? A tighter, faster admin experience. What used to take 10 minutes and a dozen clicks now takes one command and a few seconds. And because every change is logged and permission-checked, we haven’t sacrificed safety for speed.

These changes came from real pain points we felt daily. If you’re building admin panels in Filament, don’t underestimate the power of small, targeted improvements—both in the UI and behind the scenes. A well-crafted Artisan command can be just as impactful as a flashy frontend feature.

Newer post

How We Built a Scalable Status Management System in Filament PHP

Older post

How a 5-Minute Label Change Exposed Technical Debt in Our Legacy Form System