guava/filament-icon-picker
Filament Icon Picker adds an icon picker form field and matching table column to Filament panels. Choose from any installed blade-icons kit, Heroicons included by default—ideal for custom navigation, model, and frontend icons. Token-secured endpoints, configurable caching.
Install and wire up (3 commands + 1 CSS line):
composer require guava/filament-icon-picker
php artisan filament:assets
php artisan vendor:publish --tag=filament-icon-picker-config # optional, but do it early
Because the field ships its own Blade/Tailwind classes, you must have a custom Filament theme and add the package's views to the Tailwind content scan (theme.css, Tailwind v4 syntax):
@source '../../../../vendor/guava/filament-icon-picker/resources/**/*';
Skipping this is the single most common "the picker renders as an unstyled list of letters" failure — the icons exist, the CSS doesn't.
First use case — a model gets a customizable icon:
use Guava\IconPicker\Forms\Components\IconPicker;
IconPicker::make('icon')
->label('Icon');
// Careful: NOT Filament\Tables\Columns\IconColumn
use Guava\IconPicker\Tables\Columns\IconColumn;
IconColumn::make('icon');
Store it as a plain string column ($table->string('icon')->nullable()), cast nothing special, and render it on the frontend through your blade-icons kit as usual.
Where to look first, in order:
2.x, Filament 4 → 3.x, Filament 5 → 4.x/5.x. Getting this wrong produces confusing Livewire/asset errors rather than a clean composer conflict.config/filament-icon-picker.php (route prefix, middleware, cache settings) — this tells you more about runtime behaviour than the README.IconScope — this is the identity model behind "custom/uploaded" vs. "kit" icons, and understanding it makes the rest of the API predictable.Resource form + table, the bread-and-butter pattern. One field, one column, no extra plumbing:
public static function form(Schema $schema): Schema
{
return $schema->components([
IconPicker::make('icon')
->required(),
]);
}
public static function table(Table $table): Table
{
return $table->columns([
IconColumn::make('icon')->label(''),
TextColumn::make('name'),
]);
}
Restricting the icon universe per field. The package supports limiting which icon sets appear, and 5.x enforces both the scope and the allowed sets when resolving an icon server-side. Use this aggressively — a full Heroicons + Font Awesome + Phosphor palette is thousands of SVGs and a bad UX in a dropdown-sized modal. Expose the two or three sets that match your design system rather than "everything installed."
Scopes for multi-tenant / per-record palettes. Since 3.x the package has a scope concept, and 5.x centralised custom-icon scope ids in IconScope. The day-to-day pattern is: a global settings page gets the full kit; a tenant-facing "branding" panel gets a narrowed set; user-uploaded icons live in a separate scope so one tenant can't see another's uploads. If you're building anything multi-tenant, wire the scope deliberately in the form schema rather than relying on defaults.
User-uploaded icons. The package supports letting users upload their own SVGs (incoming SVGs are sanitized and labels are restricted in 5.x). Treat this as a feature flag: it's great for "let the client pick their own nav icon," and it's a moderation surface. Pair it with ->acceptedFileTypes(...)-style Filament upload validation on the underlying field and a disk you control.
Reuse the stored value for navigation & frontend. The stored value is a bare icon id (labels are cosmetic and, since 5.x, no longer carry the set prefix — "academic cap", not "Heroicon o academic cap"). That means you can safely pipe the column into:
// Panel navigation built from DB-driven settings
$navigationItems = Setting::pluck('icon', 'label');
and on the frontend <x-icon :name="$model->icon" /> with blade-icons. Keep a ?-> guard — see gotchas.
Cache deliberately. 5.x caches icon listings server-side; config lives under filament-icon-picker.cache.*. For a panel with a handful of users, the defaults are fine. For a large tenant base, check whether the cache should be per-scope and flush it whenever you deploy a new icon set — a stale listing shows an icon picker that's missing the icon you just shipped.
Integration tip: wrap it in a shared concern. If icon choice shows up on Page, Post, Category, and Tenant, don't repeat the field config six times. Extract a HasIconField trait / helper that returns a pre-configured IconPicker with your standard allowed sets and label, then call ->modifyUsing() / override per resource.
Don't forget search-result views. The README explicitly calls out "search result views" as a documented integration point — if you implement global search, the icon column needs the matching Guava column (or the correct rendering path) to appear in results.
Import collision with Filament's own IconColumn. Filament ships Filament\Tables\Columns\IconColumn. The Guava one is a different class with different behaviour (it resolves and renders the stored icon id). IDE auto-import will happily pick the wrong one and you'll get a boolean-style icon column with no error. Alias it if you use both:
use Guava\IconPicker\Tables\Columns\IconColumn as GuavaIconColumn;
Two registered routes exist and they matter. /_icon-picker/index and /_icon-picker/svgs, both secured by an encrypted per-field token. Practical consequences:
_icon-picker collides or your infra whitelists URL paths.Never render a stored icon without a null check. In 5.x getIcon() returns null for ids with no matching file. This is deliberate (an icon set was removed, or a tenant was deleted) — but it means your Blade template must survive null. $model->icon?->... or @if($model->icon).
Upgrading 4.x → 5.x is not cosmetic. The breaking changes are real and silent-ish:
fuse.js → @leeoniya/ufuzzy (client-side search behaviour and relevance ordering change).getSetJs(), getIconsJs(), getIconSvgJs() are removed — anything you wrote against those (custom Livewire views, custom pickers) breaks.CanBeCacheable is removed, and cache config moved from icon-picker.cache.* to filament-icon-picker.cache.*. A copied-over old config file will simply be ignored — the picker looks uncached and you won't see an error.Read the upgrade guide before bumping; pin 3.x on Filament 4 rather than assuming forward compatibility.
php artisan filament:assets is not a one-time step. Run it after every composer update of this package and after publishing config. Most "picker looks broken after deploy" reports are a missed asset publish or a stale theme build.
Rebuild your theme after upgrading the package. The @source glob picks up new/changed classes, but Tailwind only rescans on build. A CI pipeline that caches public/css/filament/* will serve stale CSS.
Performance: the icon list is the bottleneck, not the picker. 5.x added batching of SVG fetches and server-side listing cache, and it now resolves single icons by file lookup instead of enumerating the whole set. That last one matters for table columns: rendering 50 rows with an icon column no longer lists 5,000 SVGs. If you're on 4.x, that's a strong reason to move to 5.x.
Debugging checklist, in order: (1) custom theme exists and has @source? (2) filament:assets run? (3) correct Guava IconColumn import? (4) config published and namespace filament-icon-picker.*? (5) route prefix reachable / not blocked? (6) getIcon() returning null because the set prefix changed on upgrade?
Extension points worth knowing. The config file (routes/cache/middleware) is the primary one. Beyond that, the documented surface for scopes, allowed sets, and custom icon uploads is where you customise behaviour — the package deliberately removed the JS-accessor methods in 5.x, so treat the server-side config + schema configuration as the extension path and avoid reaching into internals.
How can I help you explore Laravel packages today?