matheusmarnt / scoutify
⌘K global search modal for Laravel — multi-model Livewire UI powered by Scout
Fund package maintenance!
Requires
- php: ^8.2
- blade-ui-kit/blade-heroicons: ^2.5
- blade-ui-kit/blade-icons: ^1.6
- http-interop/http-factory-guzzle: ^1.2
- illuminate/contracts: ^11.0|^12.0
- laravel/prompts: ^0.1|^0.3|^1.0
- laravel/scout: ^11.1
- livewire/livewire: ^3.0|^4.0
- meilisearch/meilisearch-php: ^1.16
- nikic/php-parser: ^5.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.0
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
- pestphp/pest-plugin-livewire: ^3.0|^4.0
Suggests
- algolia/algoliasearch-client-php: Algolia driver for Scout
- meilisearch/meilisearch-php: Meilisearch driver for Scout
- typesense/typesense-php: Typesense driver for Scout
- dev-main
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.2
- v1.2.1
- v1.2.0
- v1.1.0
- v1.0.1
- v1.0.0
- dev-release-please--branches--main--components--scoutify
- dev-fix/modal-panel-width
- dev-fix/modal-ui-shortcuts
- dev-fix/sail-add-services-string
- dev-feat/multi-env-install
- dev-fix/release-please-pr-number
- dev-feat/smart-url-stub
- dev-feat/auto-register-searchable
- dev-dependabot/github_actions/googleapis/release-please-action-5
- dev-dependabot/github_actions/actions/checkout-6
- dev-docs/readme-badges-logo
- dev-dependabot/composer/laravel/scout-tw-11.1
- dev-fix/pest-version-constraint
- dev-release-please--branches--main
- dev-task/artisan-commands
- dev-task/i18n
- dev-task/core-services
- dev-task/contract-concern-dto
This package is auto-updated.
Last update: 2026-04-27 13:56:04 UTC
README
Scoutify
⌘K global search modal for Laravel — multi-model Livewire UI powered by Scout.
Scoutify drops a production-ready ⌘K search experience into any Laravel application with a single Artisan command. Register your Eloquent models, choose your Scout driver — Meilisearch, Algolia, or Typesense — and ship a keyboard-triggered modal that queries multiple model types simultaneously, groups results by type, and persists recent search history to session.
The UI is a zero-JavaScript Livewire component with built-in type filtering, dark mode, WCAG AA accessibility, and mobile-first layout. Every CSS class is overridable via config/scoutify.php — no view publishing required for basic customization. Ships with translations for en, pt_BR, and es.
Features
- Livewire modal — keyboard-triggered (
⌘K/Ctrl+K) global search dialog - Multiple model types — search across any number of Eloquent models simultaneously
- Recent searches — configurable history, persisted to session
- i18n — ships with
pt_BR,en, andestranslations - Dark mode — full dark mode support out of the box
- Mobile-first — fully responsive, touch-friendly
- WCAG AA — accessible markup with focus management
- Tailwind v4 — utility classes inlined, override via config
Requirements
- PHP
^8.2 - Laravel
^11.0 || ^12.0 - Livewire
^3.0 || ^4.0 - Tailwind CSS
^4.0 - Laravel Scout
^10.0
Installation
composer require matheusmarnt/scoutify
Setup
Running Artisan commands — choose the invocation that matches your environment:
Environment Artisan invocation php artisan serve(host)php artisan <command>Laravel Sail ./vendor/bin/sail artisan <command>Docker Compose (non-Sail) docker compose exec app php artisan <command>All
scoutify:*commands below follow this pattern. Sail examples explicitly usesail artisan; Docker Compose examples usedocker compose exec app php artisan.
scoutify:install always:
- Prompts for a Scout driver (
meilisearch,algolia, ortypesense) - Installs the driver's Composer packages
- Publishes
config/scoutify.php - Sets
SCOUT_DRIVERin.env - Configures the search backend for your environment
- Runs
scoutify:doctorautomatically to verify the setup
Meilisearch
Laravel Sail
./vendor/bin/sail composer require matheusmarnt/scoutify ./vendor/bin/sail artisan scoutify:install # picks meilisearch, adds sail service, sets env vars ./vendor/bin/sail down && ./vendor/bin/sail up -d # restart to bring the meilisearch container online ./vendor/bin/sail artisan scoutify:doctor # verify connectivity ./vendor/bin/sail artisan scoutify:searchable # register models ./vendor/bin/sail artisan scoutify:import # index data
scoutify:install detects Sail automatically, runs sail:add meilisearch to add the service to docker-compose.yml, and sets MEILISEARCH_HOST=http://meilisearch:7700 in .env.
Docker Compose (non-Sail)
composer require matheusmarnt/scoutify docker compose exec app php artisan scoutify:install # writes docker-compose.scoutify.yml + sets env vars docker compose -f docker-compose.yml -f docker-compose.scoutify.yml up -d docker compose exec app php artisan scoutify:doctor # verify connectivity docker compose exec app php artisan scoutify:searchable docker compose exec app php artisan scoutify:import
scoutify:install detects an existing compose file (docker-compose.yml, compose.yaml, etc.), generates a docker-compose.scoutify.yml overlay with a Meilisearch service, and sets MEILISEARCH_HOST=http://meilisearch:7700 in .env.
Host (php artisan serve)
# Start Meilisearch first (choose one): docker run -d --name meilisearch -p 7700:7700 \ -v $(pwd)/meili_data:/meili_data getmeili/meilisearch:latest # or: https://www.meilisearch.com/docs/learn/getting_started/installation composer require matheusmarnt/scoutify php artisan scoutify:install # sets SCOUT_DRIVER + MEILISEARCH_HOST=http://localhost:7700 php artisan scoutify:doctor # verify connectivity php artisan scoutify:searchable # register models php artisan scoutify:import # index data
Typesense
Laravel Sail
sail composer require matheusmarnt/scoutify sail artisan scoutify:install # picks typesense, adds Sail service, sets env vars sail down && sail up -d sail artisan scoutify:doctor sail artisan scoutify:searchable sail artisan scoutify:import
scoutify:install runs sail:add typesense and sets TYPESENSE_HOST=typesense, TYPESENSE_PORT=8108, TYPESENSE_PROTOCOL=http, and TYPESENSE_API_KEY in .env.
Docker Compose (non-Sail)
composer require matheusmarnt/scoutify docker compose exec app php artisan scoutify:install # writes docker-compose.scoutify.yml + sets env vars docker compose -f docker-compose.yml -f docker-compose.scoutify.yml up -d docker compose exec app php artisan scoutify:doctor docker compose exec app php artisan scoutify:searchable docker compose exec app php artisan scoutify:import
Host (php artisan serve)
# Start Typesense first: docker run -d --name typesense -p 8108:8108 \ -v $(pwd)/typesense_data:/data \ typesense/typesense:latest \ --data-dir /data --api-key=xyz --enable-cors # or: https://typesense.org/docs/guide/install-typesense.html composer require matheusmarnt/scoutify php artisan scoutify:install # sets SCOUT_DRIVER + TYPESENSE_* env vars php artisan scoutify:doctor php artisan scoutify:searchable php artisan scoutify:import
Algolia
Algolia is cloud-hosted — no local service needed. scoutify:install sets SCOUT_DRIVER=algolia, installs the client package, and adds ALGOLIA_APP_ID and ALGOLIA_SECRET placeholders to .env.
composer require matheusmarnt/scoutify php artisan scoutify:install # picks algolia, sets SCOUT_DRIVER + credential placeholders # Fill in ALGOLIA_APP_ID and ALGOLIA_SECRET in .env # Get credentials at: https://www.algolia.com/ php artisan scoutify:doctor # verifies credentials are present php artisan scoutify:searchable # register models php artisan scoutify:import # index data
Diagnostics
# Host (php artisan serve) php artisan scoutify:doctor # Laravel Sail sail artisan scoutify:doctor # Docker Compose (non-Sail) docker compose exec app php artisan scoutify:doctor
Checks your driver configuration and connectivity. Reports the configured driver, the search backend URL, and whether it is reachable. Prints environment-aware remediation steps on failure:
Scout driver: meilisearch
Meilisearch host: http://meilisearch:7700
✓ Meilisearch reachable and healthy.
On failure inside a Sail container:
✗ Cannot reach Meilisearch at http://localhost:7700.
Sail detected but MEILISEARCH_HOST points to localhost (wrong inside container).
Fix: set MEILISEARCH_HOST=http://meilisearch:7700 in .env, then:
sail down && sail up -d
Exit code 0 = healthy, 1 = issue found — usable in CI health checks.
Registering Models
Make your Eloquent models globally searchable:
php artisan scoutify:searchable
The command discovers Eloquent models under app/Models/, prompts you to pick which to register (or pass --all), and automatically edits each chosen model file to:
- Import
Matheusmarnt\Scoutify\Concerns\SearchableandMatheusmarnt\Scoutify\Contracts\GloballySearchable - Add
implements GloballySearchableto the class declaration - Insert
use Searchable;as the first statement in the class body
The command also injects a concrete globalSearchUrl() method into the model, auto-resolved to the right URL for that model. All other interface methods (globalSearchTitle, globalSearchGroup, globalSearchIcon, globalSearchColor, globalSearchSubtitle) come from the Searchable trait — they already resolve dynamically based on the model name and attributes. Override them in your model at any time.
Example — registering App\Models\User with a Filament resource present:
use App\Filament\Resources\UserResource; use Illuminate\Database\Eloquent\Model; use Matheusmarnt\Scoutify\Concerns\Searchable; use Matheusmarnt\Scoutify\Contracts\GloballySearchable; class User extends Model implements GloballySearchable { use Searchable; public function globalSearchUrl(): string { return UserResource::getUrl('view', ['record' => $this]); } }
All remaining interface methods are provided by the Searchable trait with sensible defaults. Override any of them directly in your model:
// Title shown in bold in each search result row // Default: $this->name public function globalSearchTitle(): string { return $this->full_name; } // Gray subtitle line below the title (null = hidden) // Default: null public function globalSearchSubtitle(): ?string { return $this->email; } // Section header grouping results of this type // Default: class basename, e.g. "User" public static function globalSearchGroup(): string { return 'Team Members'; } // Heroicon name shown left of each result row // Default: 'heroicon-o-magnifying-glass' public static function globalSearchIcon(): string { return 'heroicon-o-user'; } // Icon tint colour (Tailwind colour name or 'gray') // Default: 'gray' public static function globalSearchColor(): string { return 'blue'; }
URL resolution cascade
When registering a model, the command detects the best URL in this order:
| Priority | Condition | Generated stub |
|---|---|---|
| 1 | Filament resource class exists | UserResource::getUrl('view', ['record' => $this]) |
| 2 | Named route {plural}.show exists |
route('users.show', $this) |
| 3 | Folio page pages/users/[user].blade.php exists |
url('/users/'.$this->getKey()) |
| 4 | None of the above | // TODO: customize URL… + url('/') placeholder |
The Searchable trait itself also applies the same cascade at runtime — so models registered with --no-stubs or those that already have the trait without a stub still resolve URLs automatically, without any extra code.
Filament conventions detected
The command probes all common Filament namespace patterns (v3, v4, v5):
App\Filament\Resources\{Model}Resource (v3)
App\Filament\Resources\{Models}\{Model}Resource (v4 per-resource folder)
App\Filament\Admin\Resources\{Model}Resource (v5 admin panel)
App\Filament\Admin\Resources\{Models}\{Model}Resource
App\Filament\Clusters\{Models}\Resources\{Model}Resource (v5 clusters)
Re-running the command is safe — it tops up only what's missing on partially-registered models. An existing globalSearchUrl() in the model is never overwritten.
Note: The registration command rewrites the model file using a PHP pretty-printer, which normalises whitespace and formatting across the entire file. Commit your model file (or ensure it's clean) before running the command if you want a minimal diff.
Use --dry-run to preview the planned edits without touching files:
php artisan scoutify:searchable --dry-run
Use --no-stubs to skip injecting globalSearchUrl() (the trait's runtime cascade still applies):
php artisan scoutify:searchable --no-stubs
Then import your models into the Scout index:
php artisan scoutify:import
Usage
Add the trigger button and modal to your layout:
<x-scoutify::gs.trigger /> <livewire:scoutify::modal />
The trigger renders a ⌘K (macOS) / Ctrl K (other) badge. The modal component wires global keyboard shortcuts automatically — Ctrl+K, ⌘K, and / (when not focused on an input) all open the modal. No extra layout markup needed.
Dispatch from anywhere:
<button x-on:click="$dispatch('scoutify:open')">Search</button>
Tailwind CSS v4
scoutify:install automatically adds the Scoutify CSS partial to your resources/css/app.css. To add it manually:
/* resources/css/app.css */ @import 'tailwindcss'; @import "../../vendor/matheusmarnt/scoutify/resources/css/scoutify.css";
The partial covers @source for all Scoutify blade views, theme tokens (--color-scoutify-accent), and the dynamic badge color safelist — no additional configuration required.
Customization
Class overrides
All UI classes are configurable via config/scoutify.php:
'classes' => [ 'trigger' => 'flex items-center gap-2 ...', 'dialog_scrim' => 'fixed inset-0 ...', 'dialog_panel' => 'relative bg-white ...', 'input' => 'w-full border-0 ...', 'toggle_active' => 'bg-blue-100 text-blue-700 ...', 'toggle_inactive'=> 'text-gray-500 ...', ],
Publish views
php artisan vendor:publish --tag=scoutify-views
Publish translations
php artisan vendor:publish --tag=scoutify-translations
Commands Reference
Prefix each command with the appropriate Artisan invocation for your environment:
| Environment | Prefix |
|---|---|
Host (php artisan serve) |
php artisan |
| Laravel Sail | sail artisan |
| Docker Compose (non-Sail) | docker compose exec app php artisan |
| Command | Description |
|---|---|
scoutify:install |
Install driver packages, publish config, and configure the search backend |
scoutify:doctor |
Verify driver configuration and backend connectivity |
scoutify:searchable |
Register models as globally searchable (injects globalSearchUrl stub) |
scoutify:searchable --no-stubs |
Register without injecting method stubs (URL resolved by trait cascade at runtime) |
scoutify:import |
Import all registered models into Scout index |
scoutify:flush |
Flush all registered models from Scout index |
scoutify:sync |
Flush then re-import (shortcut) |
Updating
# Host composer update matheusmarnt/scoutify # Laravel Sail ./vendor/bin/sail composer update matheusmarnt/scoutify # Docker Compose (non-Sail) docker compose exec app composer update matheusmarnt/scoutify
After updating, check if the config file has new keys and merge them:
php artisan vendor:publish --tag=scoutify-config --force
Note:
--forceoverwrites your published config. Back it up first or diff manually againstvendor/matheusmarnt/scoutify/config/scoutify.php.
Run scoutify:doctor to verify the setup is still healthy:
php artisan scoutify:doctor
If the new version changes Scout index structure, re-import:
php artisan scoutify:sync
Testing
composer test # Run test suite composer test:coverage # Run with coverage (requires xdebug or pcov, min 90%)
License
MIT — see LICENSE.
