Skip to content

Nova Action

Class Structure

  1. Actions should be final by default and extend either Action or DestructiveAction:
php
final class YourAction extends Action
{
}

// For destructive operations
final class DeleteSomethingAction extends DestructiveAction
{
}
  1. Actions are organized in an Nova/Actions directory within their respective module:
text
app/YourModule/Nova/Actions/YourAction.php

Common Patterns

Base Classes

  1. Regular actions extend \App\Nova\Actions\Action
  2. Destructive actions extend \App\Nova\Actions\DestructiveAction

Action Types

  1. Simple Actions
php
final class SendPasswordResetEmailAction extends Action
{
    public function handle(ActionFields $fields, Collection $members): void
    {
        // Implementation
    }
}
  1. Destructive Actions
php
final class CancelUnpaidInstallmentAction extends DestructiveAction
{
    public function handle(ActionFields $fields, Collection $installments): void
    {
        // Implementation
    }
}
  1. Actions with Fields
php
final class ExtendMembershipTimeAction extends Action
{
    public function fields(NovaRequest $request): array
    {
        return [
            DateTime::make('Extend Until'),
            Text::make('Reason'),
        ];
    }
}

Best Practices

  1. Naming Conventions

    • Use verb-noun format: SendEmailAction, CancelSubscriptionAction
    • End with "Action" suffix
    • Name should clearly indicate the action's purpose
  2. Type Safety

php
public function handle(ActionFields $fields, Collection $models): mixed
{
    /** @var \App\Modules\YourModule\Models\YourModel $model */
    foreach ($models as $model) {
        // Implementation
    }
}
  1. Validation
php
public function fields(NovaRequest $request): array
{
    return [
        Text::make('Reason')
            ->rules('required', 'min:10'),
    ];
}
  1. Error Handling
php
public function handle(ActionFields $fields, Collection $models): mixed
{
    try {
        // Implementation
    } catch (Exception $e) {
        return Action::danger('Action failed: ' . $e->getMessage());
    }
}

Examples

Simple Action

php
namespace App\Member\Nova\Actions;

use App\Nova\Actions\Action;use Illuminate\Support\Collection;use Laravel\Nova\Actions\ActionFields;

final class ResendWelcomeEmailAction extends Action
{
    public $name = 'Resend Welcome Email';

    public function handle(ActionFields $fields, Collection $models): mixed
    {
        foreach ($models as $member) {
            // Implementation
        }

        return Action::message('Welcome emails sent successfully');
    }
}

Destructive Action with Confirmation

php
namespace App\Payment\Nova\Actions;

use App\Nova\Actions\DestructiveAction;use Illuminate\Support\Collection;use Laravel\Nova\Actions\ActionFields;

final class CancelUnpaidInstallmentAction extends DestructiveAction
{
    public $name = 'Cancel Unpaid Installment';

    public function handle(ActionFields $fields, Collection $models): mixed
    {
        return $this->transaction(function () use ($models) {
            foreach ($models as $installment) {
                // Implementation
            }

            return Action::message('Installments cancelled successfully');
        });
    }

    public function confirmButtonText(): string
    {
        return 'Yes, Cancel These Installments';
    }
}

Action with Fields and Validation

php
namespace App\Member\Nova\Actions;

use App\Nova\Actions\Action;use Illuminate\Support\Collection;use Laravel\Nova\Actions\ActionFields;use Laravel\Nova\Fields\DateTime;use Laravel\Nova\Fields\Text;use Laravel\Nova\Http\Requests\NovaRequest;

final class ExtendMembershipTimeAction extends Action
{
    public $name = 'Extend Membership';

    public function fields(NovaRequest $request): array
    {
        return [
            DateTime::make('Extend Until')
                ->rules('required', 'after:today'),

            Text::make('Reason')
                ->rules('required', 'min:10')
                ->help('Explain why this membership is being extended'),
        ];
    }

    public function handle(ActionFields $fields, Collection $models): mixed
    {
        return $this->transaction(function () use ($fields, $models) {
            foreach ($models as $member) {
                // Implementation
            }

            return Action::message('Memberships extended successfully');
        });
    }
}