Starboard is a command palette for Laravel applications. It provides a powerful, extensible command interface that allows you to quickly access and interact with your application's functionality. It is intended for use within the development environment only, but you can use it in production if you so desire.
- Requirements
- Installation
- Usage
- Built in commands
- Custom Commands
- View Types Reference
- Advanced Usage
- Configuring
- Keyboard Shortcuts
- Troubleshooting
- Contributing
- Support
- License
Require the package through composer
composer require ozmos/starboard
Publish the javascript assets to ./public/vendor/starboard
php artisan vendor:publish --tag="starboard-assets"
Enable Starboard and register commands in a service provider
use Illuminate\Support\ServiceProvider;
use Ozmos\Starboard\Facades\Starboard;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Starboard::enable(app()->isLocal());
Starboard::register([
\Ozmos\Starboard\Commands\ImpersonateUser::make(),
\Ozmos\Starboard\Commands\TelescopeRequests::make(),
]);
}
}Render starboard in your application using the @starboard blade directive, most likely in your root layout
<html>
<body>
@starboard
</body>
</html>Search your User model and log in as user.
Starboard::register([
\Ozmos\Starboard\Commands\ImpersonateUser::make()
]);You can optionally configure this command further to suit your application
Starboard::register([
\Ozmos\Starboard\Commands\ImpersonateUser::make()
// title of the command (appears in initial palette list)
->commandTitle('Impersonate User')
// what model to query
->model(\App\Models\User::class)
// searchable table columns
->columns(['id', 'name', 'email'])
// customise how the users appear in the list
->itemBuilder(function (ListItemBuilder $builder, $user) {
return $builder
->title($user->name)
->subtitle($user->email)
->id($user->getKey());
})
// search filter placeholder
->placeholder('Search for a user')
// what happens when the user is selected in the list
->onSelect(function (string $id) {
$user = $this->model::find($id);
Auth::login($user);
return CommandResponse::make()->reload();
})
]);Returns the last n lines of a log file
Starboard::register([
\Ozmos\Starboard\Commands\TailLogs::make()
]);You can optionally configure the log file and number of lines returned
Starboard::register([
\Ozmos\Starboard\Commands\TailLogs::make()
->logFile(storage_path('logs/laravel.log'))
->lines(500)
]);View the most recent requests from your application. Requires telescope to be installed/enabled
Starboard::register([
\Ozmos\Starboard\Commands\TelescopeRequests::make(),
]);View the most recent mail sent from your application. Requires telescope to be installed/enabled
Starboard::register([
\Ozmos\Starboard\Commands\TelescopeMail::make(),
]);View the most recent jobs from your application. Requires telescope to be installed/enabled
Starboard::register([
\Ozmos\Starboard\Commands\TelescopeJobs::make(),
]);All commands extend from the Ozmos\Starboard\Commands\BaseCommand class. You can either extend this base class directly, or use one of the several command builders offered to speed up the process.
All commands extend BaseCommand and must implement:
id(): string- A unique identifier for the commandtitle(): string- The display name shown in the command paletterender(CommandContext $context)- Returns a view that defines what the command displays
The render method must return a view provided by the Ozmos\Starboard\Commands\Views\CommandView class. There are two main view types:
List views display a searchable list of items. Use CommandView::list() to create a list view:
return CommandView::list()
->render(function (ListBuilder $builder) {
// Return a collection of ListItemBuilder instances
return collect($items)->map(function ($item) use ($builder) {
return $builder
->item()
->title($item->name)
->subtitle($item->description)
->id($item->id);
});
})
->rerunsOnInputChange() // Re-run when user types
->placeholder('Search items...')
->onSelect(function (string $id) {
// Handle item selection
return CommandResponse::make()->push($detailCommand);
})
->action('refresh', function (string $listItemId) {
// Custom action for list items
return CommandResponse::make()->toast('Refreshed!');
});List views support:
- Async filtering: Use
rerunsOnInputChange()to filter results as the user types - Pagination: Return a paginated result from your query (Laravel's paginator is supported)
- Actions: Add custom actions that appear as buttons for each list item
- Placeholder text: Customize the search input placeholder
Detail views display detailed information about a single item. Use CommandView::detail() to create a detail view:
return CommandView::detail()
->render(function (DetailBuilder $builder) {
return $builder
->text('Label', 'Value') // Plain text field
->preformatted('Code', $code) // Preformatted text (monospace)
->markdown('Description', $markdown) // Markdown content (rendered as HTML)
->html('Content', $html, iframe: false) // Raw HTML content
->action('refresh', function () {
return CommandResponse::make()
->replace($this)
->toast('Refreshed!');
});
});Detail views support:
- Text blocks: Simple label-value pairs
- Preformatted blocks: Monospace text (great for code, JSON, etc.)
- Markdown blocks: Rendered markdown content
- HTML blocks: Raw HTML (with optional iframe rendering)
- Actions: Add custom action buttons
If your command operates around searching a list of models you can use the ModelLookup command builder
Starboard::register([
\Ozmos\Starboard\Commands\ModelLookup::make()
// title of the command (appears in initial palette list)
->commandTitle('Team Lookup')
// id of the command (must be unique across all registered commands)
->commandId('team-lookup')
// what model to query
->model(\App\Models\User::class)
// searchable table columns
->columns(['id', 'name', 'email'])
// customise how the users appear in the list
->itemBuilder(function (ListItemBuilder $builder, $user) {
return $builder
->title($user->name)
->subtitle($user->email)
->id($user->getKey());
})
// search filter placeholder
->placeholder('Search for a user')
// what happens when the user is selected in the list
->onSelect(function (string $id) {
$user = $this->model::find($id);
Auth::login($user);
return CommandResponse::make()->reload();
})
]);Here we will build a complete example that demonstrates:
- Creating a custom command that returns a list of items
- Creating a child command that shows detail views
- Using CommandResponse to navigate between commands
- Using actions for additional functionality
First, let's create a command that searches through a list of programming languages:
use Ozmos\Starboard\Commands\BaseCommand;
use Ozmos\Starboard\Commands\CommandContext;
use Ozmos\Starboard\Commands\CommandResponse;
use Ozmos\Starboard\Commands\Views\CommandView;
use Ozmos\Starboard\Commands\Views\ListBuilder;
class SearchLanguages extends BaseCommand
{
public function id(): string
{
return 'search-languages';
}
public function title(): string
{
return 'Search Languages';
}
public function render(CommandContext $context)
{
$languages = ['PHP', 'JavaScript', 'Python', 'Ruby', 'Go', 'Rust'];
return CommandView::list()
->render(function (ListBuilder $builder) use ($context, $languages) {
return collect($languages)
->when($context->input(), fn($query) => $query->filter(
fn($language) => str_contains(strtolower($language), strtolower($context->input()))
))
->map(function ($language) use ($builder) {
return $builder
->item()
->title($language)
->id($language);
});
})
->rerunsOnInputChange()
->placeholder('Search for a programming language...')
->onSelect($this->onSelect(...));
}
private function onSelect(string $language)
{
return CommandResponse::make()->push(LanguageDetail::make()->forLanguage($language));
}
public function children(): array
{
return [
LanguageDetail::make()->hidden(),
];
}
}Now, let's create the detail command that shows information about a selected language:
use Ozmos\Starboard\Commands\BaseCommand;
use Ozmos\Starboard\Commands\CommandContext;
use Ozmos\Starboard\Commands\CommandResponse;
use Ozmos\Starboard\Commands\Views\CommandView;
use Ozmos\Starboard\Commands\Views\DetailBuilder;
class LanguageDetail extends BaseCommand
{
public string $language;
public function forLanguage(string $language): self
{
$this->language = $language;
return $this;
}
public function id(): string
{
return 'language-detail';
}
public function title(): string
{
return 'Language Detail';
}
public function render(CommandContext $context)
{
return CommandView::detail()
->render(function (DetailBuilder $builder) {
$info = $this->getLanguageInfo($this->language);
return $builder
->text('Name', $this->language)
->text('Type', $info['type'])
->text('Year Created', $info['year'])
->preformatted('Description', $info['description'])
->markdown('Features', $info['features'])
->action('view-docs', fn() => CommandResponse::make()
->openUrl($info['docs_url'])
->toast('Opening documentation...'));
});
}
private function getLanguageInfo(string $language): array
{
// Your logic to fetch language information
return [
'type' => 'Interpreted',
'year' => '1995',
'description' => 'A popular programming language...',
'features' => '- Feature 1\n- Feature 2',
'docs_url' => "https://example.com/docs/{$language}",
];
}
}Register your custom commands:
Starboard::register([
SearchLanguages::make(),
]);When a user interacts with your command (selects an item, clicks an action), you return a CommandResponse that tells Starboard what to do next:
push($command)- Navigate to a new command (adds to navigation stack)replace($command)- Replace the current commandopenUrl($url)- Open a URL in a new tabreload()- Reload the current pagedismiss()- Close the command palettetoast($message)- Show a toast notification (can be chained with other responses)
The CommandContext provides information about the current state:
$context->input()- The current search input from the user$context->page()- The current page number (for pagination)$context->cursor()- The pagination cursor (for cursor-based pagination)
Child commands are commands that are automatically registered when their parent is registered, but are marked as hidden (not discoverable in the root command list). They're typically used for detail views or sub-commands that are only accessible through their parent.
To create a child command, return it from the children() method and mark it as hidden:
public function children(): array
{
return [
LanguageDetail::make()->hidden(),
];
}render(Closure $callback)- Define how list items are generated. The callback receives aListBuilderand should return a collection ofListItemBuilderinstances or a paginator.item()- Create a new list item builderplaceholder(string $text)- Set the search input placeholderrerunsOnInputChange(bool $async = true)- Enable automatic re-rendering when the user typesonSelect(Closure $callback)- Handle item selection. Callback receives the item ID and should return aCommandResponseaction(string $name, Closure $callback)- Add a custom action button. Callback receives the list item ID
id(string $id)- Set the unique identifier for this itemtitle(string $title)- Set the main title textsubtitle(?string $subtitle)- Set optional subtitle text
render(Closure $callback)- Define the detail content. The callback receives aDetailBuildertext(string $label, string $value)- Add a plain text fieldpreformatted(string $label, string $value)- Add preformatted (monospace) textmarkdown(string $label, string $value)- Add markdown content (rendered as HTML)html(string $label, string $value, bool $iframe = false)- Add raw HTML contentaction(string $name, Closure $callback)- Add a custom action button
push(BaseCommand $command)- Navigate to a new command (adds to navigation history)replace(BaseCommand $command)- Replace the current command (no history)openUrl(string $url)- Open a URL in a new browser tabreload()- Reload the current pagedismiss()- Close the command palettetoast(string $message)- Show a toast notification (can be chained)
input()- Get the current search input valuepage()- Get the current page number (for pagination)cursor()- Get the pagination cursor (for cursor-based pagination)
When using ModelLookup, you can override the default query logic:
\Ozmos\Starboard\Commands\ModelLookup::make()
->model(\App\Models\User::class)
->columns(['name', 'email'])
->query(function (CommandContext $context) {
// Custom query logic
return \App\Models\User::query()
->where('active', true)
->when($context->input(), function ($query) use ($context) {
$query->where('name', 'like', '%' . $context->input() . '%');
})
->orderBy('created_at', 'desc')
->limit(20);
})List views support Laravel's pagination. Simply return a paginated result:
return CommandView::list()
->render(function (ListBuilder $builder) use ($context) {
return \App\Models\User::query()
->when($context->input(), fn($q) => $q->where('name', 'like', '%' . $context->input() . '%'))
->paginate(perPage: 10, page: $context->page())
->through(function ($user) use ($builder) {
return $builder
->item()
->title($user->name)
->id($user->id);
});
});Add custom actions to list items that appear as buttons:
return CommandView::list()
->render(function (ListBuilder $builder) {
// ... build items
})
->action('edit', function (string $itemId) {
return CommandResponse::make()
->openUrl("/users/{$itemId}/edit")
->toast('Opening editor...');
})
->action('delete', function (string $itemId) {
\App\Models\User::find($itemId)->delete();
return CommandResponse::make()
->replace($this)
->toast('User deleted');
});Making Commands Hidden
Commands that shouldn't appear in the root command list can be marked as hidden:
$command->hidden();Hidden commands are typically child commands that are only accessible through their parent command.
Publish the config to config/starboard.php
php artisan vendor:publish --tag="starboard-config"
The configuration file allows you to customize the routing for Starboard endpoints:
return [
'routes' => [
'prefix' => '_starboard', // URL prefix for Starboard routes
'middleware' => ['web'], // Middleware to apply to routes
'name' => 'starboard.', // Route name prefix
],
];Starboard supports the following keyboard shortcuts:
Cmd+K/Ctrl+K- Open/close the command paletteEsc- Close the command palette or go back↑/↓- Navigate through list itemsEnter- Select the highlighted itemCmd+Enter/Ctrl+Enter- Execute the first action (if available)
- Ensure Starboard is enabled:
Starboard::enable(true) - Check that commands are registered in a service provider's
boot()method - Verify commands are marked as discoverable (not hidden)
If you see "Command already registered" errors, ensure all command IDs are unique. You can override the id() method to provide a custom identifier.
Make sure you've published the assets:
php artisan vendor:publish --tag="starboard-assets"
And that the @starboard directive is included in your layout file.
Contributions are welcome! Please feel free to submit a Pull Request.
For issues, questions, or feature requests, please visit the GitHub repository.
Starboard is open-sourced software licensed under the MIT license.
