Ctrl+K search
Admin 0.20 adds one search box for the whole panel. Press Ctrl+K (⌘K on a Mac), or / when the cursor is not in a field, or click the search field in the top bar. Type a few letters and the results arrive as you type, grouped:
- Pages and actions: Dashboard, Media, Users, Settings, Activity, Trash, My profile, and "New post" for every resource the person may add to.
- Records, one group per resource, in sidebar order.
- Settings pages, found by their name, description and field labels.
- Media items, by title, file name and alt text, with a thumbnail.
- The site's own groups (below).
Each group shows up to 5 results. When a resource has more, See all in Posts opens its list already searched (the list's own search, which does not match ids). At most 8 resource groups are shown; add words to narrow the search. Before typing, the box shows the last 8 records the person opened and the panel's pages.
| Key | Does |
|---|---|
| ↑ ↓ | Move through the results (and the "See all" links); they wrap round |
| Enter | Open the result |
| Ctrl+Enter (⌘Enter) | Open it in a new tab |
| Esc | Close |
#42 (or 42) finds the record with id 42 in every resource. On phones the field is a search icon and the box fills the screen. Without JavaScript the field opens a results page with the same groups.
Nothing is installed or migrated: search reads the tables the panel already knows.
What is found
A record is found by the columns its table marks ->searchable(), the same columns the list's own search box uses, in every language of a translated column. So the list and Ctrl+K always agree.
public function table(Table $table): Table
{
return $table->columns([
TextColumn::make('title')->searchable(), // the result's title, and what is matched
TextColumn::make('slug')->searchable(), // matched; shown as the detail line
TextColumn::make('price'), // not searched
]);
}
- The title of a result is the record's title as the list shows it, in the panel's language.
- The detail line is the next searchable column.
- A title that starts with the typed words comes first, then the newest records.
- A resource without searchable columns is found only by
#id.
Who sees what
Search shows only what the person may open:
- a resource's records need its
viewpermission, and they stay inside the resource'squery()(one site, one owner); - a record opens its edit page when the person has the
updatepermission andcanEdit()allows it, otherwise the list searched for its title; - settings pages need their permission, media items
media.view, Usersusers.manage, Activityactivity.view; - "New …" actions need the
createpermission andcanCreate(); - recent records are read again every time they are shown, through the resource's
query()andcanEdit(), with their current title; deleted ones leave the list.
The trash and the activity log are not searched; each keeps its own search.
Your own resources
final class ListingResource extends Resource
{
// Leave this resource out of Ctrl+K (its list search still works)
protected static bool $globallySearchable = false;
// The line under the title (default: the next searchable column)
public function searchDetails(object $record): ?string
{
return $record->city . ' · ' . number_format((float) $record->price) . ' €';
}
// Where the result goes (default: the edit page, or the list searched for it)
public function searchUrl(object $record): ?string
{
return null;
}
}
Your own results
A site can add groups, for example its help pages, in config/admin.php:
'search' => [
'sources' => [App\Admin\Search\HelpArticles::class],
],
namespace App\Admin\Search;
use FloCMS\Admin\Admin;
use FloCMS\Admin\Search\SearchResult;
use FloCMS\Admin\Search\SearchSource;
final class HelpArticles implements SearchSource
{
public function label(): string
{
return 'Help';
}
/** @return list<SearchResult> */
public function search(string $query, int $limit): array
{
return [
SearchResult::make('Upload a gallery', Admin::url('help/gallery'))->detail('Media')->icon('image'),
];
}
}
A source returns only what the current person may open: the panel checks nothing more for it. Links must be a site path (/…) or an http(s) address; any other result is dropped. A source that throws is skipped and logged.
Settings
// config/admin.php
'search' => true, // the default
'search' => false, // no search field, no Ctrl+K, /admin/search answers 404
'search' => ['per_group' => 3], // results per group, 1 to 10 (default 5)
'search' => ['shortcut' => false], // keep Ctrl+K and / for the browser; the field stays
Limits
- Contains, not fuzzy. "vila" does not find "villa". Arabic letters are matched as typed (أ and ا differ).
- Large tables. "Contains" reads the whole table; it is quick up to tens of thousands of rows per resource. A big log table can be left out with
$globallySearchable = false, or keep fewer searchable columns. - Time budget. Groups are asked one after another; after 1.5 seconds no new group starts, and the box says some were skipped (as it does past 8 resource groups).
- Detail lines show the stored value of the next searchable column; use
searchDetails()for anything formatted. - Recent records live in the session: a new sign-in starts with an empty list.