opscale-co/nova-toolbar-actions
A Laravel Nova package that adds configurable toolbar actions to resources, placing quick-access buttons for common tasks above resource tables. Define actions once and expose them cleanly in the Nova UI without custom frontend work, simplifying repetitive admin workflows.
opscale-co/nova-toolbar-actions is a Laravel Nova add-on that surfaces Nova Actions in the resource index toolbar (alongside "Create") instead of the per-row dropdown. v1.0.0 is the first tagged release, so treat the API as unproven and read the source before leaning on it in production.
Minimal steps:
composer require opscale-co/nova-toolbar-actions
Nova packages are auto-discovered, so there is normally no provider registration step — verify with php artisan about or php artisan package:discover if the toolbar does not appear.
First place to look: a Nova Resource's action registration. The package's entry point is almost certainly a trait or a method override on App\Nova\Resource (e.g. toolbarActions() or a ToolbarActions trait/interface). Open vendor/opscale-co/nova-toolbar-actions/src and read the service provider plus the trait — at 1.0.0 that is faster and more reliable than any docs.
First use case: a single "Export selected / Export all" style action on a large index. Generate a standard Nova action (php artisan nova:action ExportRecords), then register it in the toolbar slot rather than actions():
// app/Nova/Resource.php
public static function toolbarActions(Request $request): array
{
return [new \App\Nova\Actions\ExportRecords];
}
Then confirm: the button renders on the index, opens the action modal, and handle() receives the expected $models collection.
Register on the base Resource, not per-resource. Toolbar actions are a cross-cutting concern; putting the override on App\Nova\Resource and filtering inside the method keeps every resource consistent and avoids copy-paste drift:
public static function toolbarActions(Request $request): array
{
return array_filter([
Nova::whenServing(fn () => new ExportRecords, fn () => null),
]);
}
Scope by resource class when a toolbar action only makes sense on some models:
public static function toolbarActions(Request $request): array
{
return static::class === \App\Nova\Order::class
? [new BulkResendInvoices]
: [];
}
Design for both contexts. A toolbar action is ambiguous about selection: it can be clicked with nothing selected, with a page selected, or "select all matching". Handle all three explicitly in handle():
public function handle(ActionFields $fields, Collection $models)
{
if ($models->isEmpty()) {
return Action::danger('Select at least one row.');
}
// dispatch a job per chunk rather than looping inline
$models->chunk(500)->each(fn ($chunk) => ExportChunk::dispatch($chunk->modelKeys()));
return Action::message('Export queued.');
}
Pair with showOnTableRow() / onlyOnIndex() deliberately. Keep destructive toolbar actions out of the row dropdown so there is exactly one affordance for "delete all selected", and always return an Action::confirm() modal for anything irreversible.
Compose with Nova's built-in export and filters. Toolbar actions read the filtered index query in most implementations — sanity-check this early, because "act on what the user is currently looking at" is the whole value of a toolbar versus a global command.
first release, Initial commit, a semantic-release dependency fix). Budget an hour to read src/ and write your own notes; do not assume the method signature from the package name."opscale-co/nova-toolbar-actions": "1.0.*" and re-test on every bump.authorize() on the Action gates the button, but toolbar rendering may not call it — check authorize() runs before the button paints, otherwise non-privileged users see a button that 403s on click.$models may be empty or may be the full filtered set. Instrument handle() with a Log::info($models->count()) on first integration; behaviour differs between "none selected" and "select all".fields(), confirm the modal opens from the toolbar — some toolbar implementations pass a different request payload than the row-dropdown path, which breaks dependent fields (dependsOn).Action::message() over Action::redirect() unless you have tested the return path.handle() will hit PHP max_execution_time. Dispatch jobs and return a message immediately.config/nova-toolbar-actions.php), publish it with php artisan vendor:publish --tag=nova-toolbar-actions-config before editing — you want your customisations diffable against upstream defaults.php artisan nova:publish after install, clear php artisan optimize:clear, and check the browser console for a failed Nova asset chunk — the button often fails silently on asset mismatch rather than throwing server-side.How can I help you explore Laravel packages today?