ibrahim-bougaoua/filaprogress
A Filament plugin for adding progress bars and progress columns to your admin panel. Show completion percentages in tables and forms, with configurable colors, heights, and labels for quick at-a-glance status tracking.
ibrahim-bougaoua/filaprogress is a small Filament plugin that adds progress bars and progress-tracking helpers to admin panels. It is a thin convenience layer — you still own the data model and the math that produces a percentage; the package owns the rendering.
Minimum viable setup
composer require ibrahim-bougaoua/filaprogress
php artisan vendor:publish --tag="filaprogress-config"
php artisan vendor:publish --tag="filaprogress-assets" # if the tag exists in your version
php artisan filament:assets
Register the plugin in your panel provider:
use IbrahimBougaoua\Filaprogress\FilaprogressPlugin;
public function panel(Panel $panel): Panel
{
return $panel
->plugins([
FilaprogressPlugin::make(),
]);
}
Then clear the caches, because Filament plugins and Blade views are aggressively cached:
php artisan optimize:clear && php artisan filament:cache-components
Where to look first
README.md — with 34 stars and a single maintainer, the README is the contract. Treat it as the primary source of truth over any blog post.config/filaprogress.php — the published config tells you what is actually tunable (colors, sizes, default polling, view paths).src/ — the component classes. If a widget or column class exists, its public setters are your API surface.resources/views/ — the Blade files. If the public API is thin, you will extend or override these.First use case
Start with the lowest-risk integration: a single progress bar on a dashboard widget showing completion for one domain concept (e.g. "Onboarding completion across active accounts"). Do not wire it into every Resource on day one. Get one bar rendering with real data, confirm the color thresholds behave in both light and dark mode, then expand.
Keep the math in one place so every surface (widget, column, API, test) agrees.
// app/Models/Project.php
public function getProgressPercentAttribute(): int
{
$total = $this->tasks()->count();
if ($total === 0) {
return 0; // never divide by zero; decide if 0 or 100 is correct for you
}
$done = $this->tasks()->where('status', 'done')->count();
return (int) round($done / $total * 100);
}
This is the single most important pattern. The package renders a number; you must guarantee the number is sane and cheap to compute.
Aggregate progress belongs on a widget, not a table. If the package ships a progress widget class, extend it and override the data source method rather than reimplementing the view.
class OnboardingProgressWidget extends ProgressWidget
{
protected static ?int $sort = 2;
protected function getProgress(): int
{
$total = Account::count();
return $total === 0
? 0
: (int) round(Account::whereNotNull('onboarded_at')->count() / $total * 100);
}
}
Register it explicitly in the panel (->widgets([...])) if auto-discovery is unreliable in your setup.
The highest-value day-to-day pattern: a progress column on a resource list, so an admin can scan a hundred rows and see which ones are stuck.
Tables\Columns\TextColumn::make('progress_percent')
->label('Progress')
->formatStateUsing(fn ($state) => "{$state}%")
->badge()
->color(fn ($state) => match (true) {
$state >= 100 => 'success',
$state >= 50 => 'warning',
default => 'danger',
});
If the package provides a dedicated progress column, prefer it — but keep the threshold logic in your own match, because it is domain-specific and you will change it far more often than the package.
Performance caution: progress_percent as an accessor triggers a count() per row. On a 50-row table that is 100 queries. Either eager-load with withCount, or persist a progress_percent column updated by an observer/job. This is the difference between a snappy panel and a timeout complaint.
Progress on a detail page is the natural place for richer context — include the raw counts alongside the bar so the percentage is interpretable.
Infolists\Infolist::make()->schema([
Infolists\Components\TextEntry::make('progress_percent')
->label('Completion')
->suffix('%'),
// ...your counts, milestones, timestamps
]);
For onboarding flows, track discrete completion flags rather than a free-floating number:
public function getOnboardingProgressAttribute(): int
{
$steps = [
'profile_completed' => $this->profile_completed,
'billing_added' => $this->billing_added,
'first_project_created' => $this->projects()->exists(),
'team_invited' => $this->members()->count() > 1,
];
return (int) round(collect($steps)->filter()->count() / count($steps) * 100);
}
Discrete steps are auditable and testable — you can assert exactly which step is missing.
If the bar should move without a refresh, use Filament's polling on the widget. Keep the interval conservative (10s+, not 1s) and only poll widgets that are actually visible. Polling is the most common cause of "the admin panel feels slow" reports with progress dashboards.
If you need different markup (extra label, tooltip, stacked segments), publish/override the Blade view rather than copying the component class. Vendoring the class means you inherit the maintenance burden every time Filament ships a major version.
Filament version compatibility is the #1 risk. This package's last release was September 2024, which brackets it tightly around Filament 3.x. Verify the composer.json constraints before committing. If you are on Filament 4, expect friction — check the issue tracker for open compatibility threads and be prepared to patch a view yourself. With 34 stars and zero dependents, there is no ecosystem pressure forcing a quick fix.
Publish tags are not guaranteed. If vendor:publish --tag="filaprogress-config" reports "no publishable resources," run php artisan vendor:publish interactively and read the provider names — the tag string in the README may differ from the one registered in FilaprogressServiceProvider.
Assets silently 404. After install, a blank or unstyled bar almost always means filament:assets was not re-run, or the public assets are stale. Fix: php artisan filament:assets && php artisan optimize:clear.
Clamp your percentages. Anything rendering a width from a percentage should be clamped to 0..100. Negative values (from a bad subtraction) or values above 100 (from double-counting a join) produce broken layouts that look like package bugs but are data bugs. Defensive clamping in the accessor costs nothing.
The empty-collection case is a product decision. 0/0 tasks — is the project 0% complete or 100% complete? The package will render whatever you give it. Decide explicitly, document it, and test it. This is the most common source of "the progress bar is wrong" tickets.
Dark mode and contrast. Progress bar colors that read well on white are often muddy on Filament's dark surface. Check both modes for each threshold color you use. Filament semantic colors (success, warning, danger) generally adapt; hardcoded hex values do not.
Avoid N+1 on list pages. As noted above, accessor-driven progress columns are query-multipliers. Use withCount or a persisted column. Verify with DB::listen or Laravel Debugbar before shipping a table with a progress column to a real dataset.
Test the math, not the bar. Do not write Livewire tests asserting the package renders a div. Write plain unit tests on your progress accessor: all-done, none-done, empty, and partial. The package's rendering is the vendor's problem; the arithmetic is yours.
Pin the version. With one maintainer and a low release cadence, composer require without a constraint leaves you exposed to a surprise breaking tag. Use "ibrahim-bougaoua/filaprogress": "^1.0" and commit the lockfile.
Have an exit ramp. The component is likely a div with a width style. If the package stalls, replacing it with a Filament ViewColumn or a custom Blade partial is an afternoon of work — provided you kept the percentage computation in your own model accessors and did not scatter package-specific calls through your resources. That separation is the real insurance policy.
Report upstream with a reproduction. If you do hit a bug, the maintainer is one person. An isolated repo with the minimal Filament panel, the exact version matrix, and a screenshot is far more likely to get a fix than a vague issue — and you will need that repro anyway to justify patching locally.
How can I help you explore Laravel packages today?