---
url: /custom-modules/global-search.md
---
# Global Search and Command Palette

Add your resources to the Evolve global search, and your screens and actions to the Cmd-K palette.

## Implement the `Searchable` interface

Your models should implement the `Searchable` interface and define the following functions:

```php[YourModel.php]
public function scopeSearch(Builder $query, string $keyword)
{
   // Eloquent query to search for a given `$keyword`
}

public function getSearchableTitle(): string
{
    // Define the title for a search result
}

public function getSearchableDescription(): ?string
{
    // Define the description for a search result
}

public function getSearchableAdminUrl(): string
{
    // Define the backend route for when a user 
    // clicks on a search result in the control panel
}
```

## Link blocks to your editor&#x20;

Search results, the activity log and the media usage list also show single blocks. A click on one opens the editor of the record the block sits on. If your model carries blocks (`use HasBlocks`), implement `BlockContainer` and return the URL of its block editor. Add `?blockId=` so the editor opens the block directly:

```php[YourModel.php]
use Racerfish\Evolve\Contracts\BlockContainer;
use Racerfish\Evolve\Models\Block;

class Order extends Model implements BlockContainer
{
    use HasBlocks;

    public function blockEditUrl(Block $block): string
    {
        return route('shop.order.edit', $this->getKey()) . '?blockId=' . $block->getKey();
    }
}
```

The route must render the block editor (`@livewire('evolve::block.editor', ['blockable' => $item])`). Nested blocks ask their parent block, so a card inside a cards block opens in the same editor. A model without the contract links to the page editor, which only fits pages. Pages, sections and all models of the bundled modules implement it.

## Register searchable models

Register your searchable models in `config/evolve.php` or in the module registration `searchable` option:

```php
Evolve::registerModule('blog', [
    // ...
    'searchable' => [
        Post::class,
    ]
]);
```

Optionally name the heading of your results in the Cmd-K palette. Without it the plural class name is used:

```php
public static function getSearchableGroupLabel(): string
{
    return __('shop::views.orders');
}
```

Content search is limited to the roles in `evolve.roles.search` (default `developer`, `admin`, `content_manager`).

## Command palette

Cmd+K lists panel screens (views) and things to start (actions) next to the content results. Each entry is a `PanelCommand` pointing at a named route. It only shows when the route exists and the user passes every `role:` middleware on it, so you rarely need to repeat roles.

Modules pass their commands with the registration. The module label (or name) is shown next to each entry:

```php
use Racerfish\Evolve\Support\PanelCommand;

Evolve::registerModule('blog', [
    // ...
    'label' => 'blog::views.blog', // optional, defaults to 'name'
    'commands' => [
        PanelCommand::view('blog::views.posts', 'blog.post.index')->icon('newspaper'),
        PanelCommand::view('blog::views.categories', 'blog.category.index')->icon('newspaper'),
        PanelCommand::action('blog::views.new-post', 'blog.post.create')->keywords(['article', 'artikel']),
    ],
]);
```

A host app registers its own in a service provider. A closure runs per request and can build commands from data:

```php
use Racerfish\Evolve\Facades\Evolve;
use Racerfish\Evolve\Support\PanelCommand;

Evolve::registerCommands([
    PanelCommand::view('Orders', 'app.order.index')->icon('bag')->keywords('bestellungen'),
    PanelCommand::action('New order', 'app.order.index', ['create' => 'true']),
    fn () => Shop::pluck('name', 'id')->map(
        fn (string $name, int $id) => PanelCommand::view($name, 'app.shop.show', ['shop' => $id])->icon('bag')
    ),
]);
```

* `view()` and `action()` take a label (translation key or plain text), a route name and route parameters. Actions get the `plus` icon.
* `->icon()` takes a panel icon name (or a Font Awesome alias).
* `->keywords()` adds search terms in any language. The English label is searchable anyway.
* `->context()` sets the muted text next to the label.
* `->roles('admin|developer')` narrows an entry further than its route does.
* For a list with a drawer, point the action at the list with `['create' => 'true']`. For a resource with its own create page, point it at the `.create` route.
