Why Command Palettes Matter

If you pay attention to the tools professional developers reach for every day, you will notice a pattern: the best ones all have a command palette.

VS Code⌘P / Ctrl+P for files, ⌘Shift+P for commands. GitHubT for file search, Ctrl+K for the full command menu. Linear⌘K for everything. Figma⌘/ for the search overlay. Raycast — the entire product is a command palette.

The reason these interfaces converge on this pattern is not aesthetic. It is fundamental: experienced users think in terms of what they want to do, not where in the UI the relevant control lives. A command palette bridges the gap between intention and action with zero navigation overhead.

Filament panels are built by experienced developers for experienced operators. They deserve keyboard-first navigation.

The Architecture Problem

The first challenge in building a command palette for Filament is the registration architecture. A useful palette needs to know about every resource, every action, and every navigation item in the panel — but Filament panels are assembled at runtime through service providers and plugin boot hooks. There is no single central registry to read from.

The solution was to build a CommandPaletteManager singleton that collects registrations during the panel boot phase. Each Filament resource, page, and action fires a registration event that the manager intercepts and indexes.

class CommandPaletteManager
{
    protected array $commands = [];
    protected array $sections = [];

    public function register(PaletteCommandContract $command): void
    {
        $this->commands[$command->getId()] = $command;
    }

    public function section(string $label): CommandSection
    {
        return $this->sections[$label] ??= new CommandSection($label);
    }

    public function search(string $query): Collection
    {
        if (empty($query)) {
            return collect($this->commands)->values();
        }

        return collect($this->commands)
            ->filter(fn ($cmd) => str_contains(
                strtolower($cmd->getLabel()),
                strtolower($query)
            ))
            ->values();
    }
}

The manager is bound in the service container as a singleton:

$this->app->singleton(CommandPaletteManager::class);

The Search Engine

The palette overlay is a Livewire component that renders as a modal and handles keyboard events through Alpine.js. The search itself is handled server-side — the Livewire component's search property is synced with the Alpine.js input field, and every keypress fires a Livewire update that calls CommandPaletteManager::search().

For performance, the manager's command index is cached in the application cache at boot time. Subsequent searches hit the in-memory resolved cache, keeping response times under 50ms even for large panels with hundreds of registered commands.

class CommandPaletteComponent extends LivewireComponent
{
    public string $search = '';
    public array $results = [];

    public function updatedSearch(): void
    {
        $manager = app(CommandPaletteManager::class);
        $this->results = $manager->search($this->search)->toArray();
    }

    public function execute(string $commandId): void
    {
        $manager = app(CommandPaletteManager::class);
        $command = $manager->find($commandId);
        $command->execute($this);
    }
}

Supporting Filament v3, v4, and v5

One of the explicit design goals was cross-version compatibility. Filament v3, v4, and v5 have different internal APIs for action registration and resource discovery. Managing this without three separate code paths required an abstraction layer.

The solution was a FilamentVersionAdapter interface with concrete implementations for each Filament major version. The service provider detects the installed Filament version and binds the correct adapter:

$filamentVersion = (int) explode('.', InstalledVersions::getVersion('filament/filament'))[0];

match ($filamentVersion) {
    3 => $this->app->bind(FilamentVersionAdapter::class, FilamentV3Adapter::class),
    4 => $this->app->bind(FilamentVersionAdapter::class, FilamentV4Adapter::class),
    5 => $this->app->bind(FilamentVersionAdapter::class, FilamentV5Adapter::class),
    default => throw new UnsupportedFilamentVersionException($filamentVersion),
};

The Interactive Installer

The guided installer is built with Laravel's Illuminate\Console\Command and the symfony/console Question helpers. It walks through:

  1. Choosing a trigger keyboard shortcut
  2. Enabling or disabling auto-discovery of Filament resources
  3. Configuring which sections appear in the palette
  4. Publishing the configuration file
  5. Registering the plugin in AdminPanelProvider.php

The final step — editing the panel provider — uses a simple regex replacement to inject the plugin registration line, avoiding the need for full PHP AST parsing while remaining reliable for the standard provider format.

Keyboard UX Details

The overlay handles three key interaction patterns:

  • Arrow keys — navigate the results list; selection wraps at the top and bottom
  • Enter — execute the currently selected command
  • Escape — close the palette and return focus to the underlying page

Focus management is handled through Alpine.js $focus magic to ensure the input receives focus when the overlay opens and the underlying page receives focus when it closes.

GitHub Repository

The source code, interactive installer, and full configuration reference are available on GitHub.

Conclusion

Filament Command Palette Pro is an opinionated take on what Filament admin panels should feel like for power users. The command palette pattern has been proven in the most successful developer tools of the past decade — there is no reason Filament panels should be left out. If you spend significant time in Filament panels, keyboard-first navigation is worth adding. The package makes it a five-minute install.