Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Callbacks Laravel Package

ganyicz/nova-callbacks

Define Eloquent model event callbacks such as creating, updating, and deleting directly inside your Laravel Nova resources. Keeps event logic close to related fields and removes the need for separate observers.

View on GitHub
Deep Wiki
Context7

Getting Started

Read the source before you read the docs. The package is roughly a hundred lines of glue that wraps Nova's resource store()/update()/destroy() flow and invokes methods on your resource. The last release is from June 2022, so the README and any blog posts you find may predate your Nova version. Five minutes in vendor/ is more authoritative than either.

composer require ganyicz/nova-callbacks
grep -rn "function after\|trait \|class " vendor/ganyicz/nova-callbacks/src

That grep gives you the exact hook names and argument order on your installed version. Everything below assumes the README shape — callbacks declared on the resource, receiving the affected model and the NovaRequest — but verify the signature before you write twenty of them.

Prerequisites: a licensed Nova install (CI needs NOVA_USERNAME / NOVA_PASSWORD), PHP 8.x, and a pinned Nova major. A clean composer require against a Nova version newer than 2022 is necessary but not sufficient — it does not prove the callbacks still fire. Prove that with a test.

First use case: an audit trail. It is the canonical callback — no external I/O, safe under retry, and easy to assert. Write the failing test first against the lowest-risk resource you own:

it('writes an audit row when an admin updates a user', function () {
    $admin = User::factory()->admin()->create();
    $user  = User::factory()->create(['status' => 'active']);

    $this->actingAs($admin, 'web')
        ->putJson("/nova-api/users/{$user->id}", [
            'status' => 'suspended',
            // ...remaining required fields
        ])
        ->assertSuccessful();

    expect(AuditLog::where('subject_id', $user->id)->exists())->toBeTrue();
});

Run it red, add the callback, run it green. If it never goes green, you have learned the package is dead on your Nova version for the cost of an afternoon — which is the cheapest possible outcome.

First real callback:

class User extends Resource
{
    public static $model = \App\Models\User::class;

    public function fields(NovaRequest $request) { /* ... */ }

    // Confirm exact name/signature from source before using
    public function afterUpdate(NovaRequest $request, $model): void
    {
        AuditLog::create([
            'user_id'    => $request->user()->id,
            'subject_id' => $model->id,
            'changes'    => $model->getChanges(),
        ]);
    }
}

Implementation Patterns

Put an interface between the package and your resources. Don't sprinkle package-specific method names across fifty resource classes. Define one contract and implement it in a base resource:

abstract class Resource extends NovaResource
{
    // the single place that knows about ganyicz/nova-callbacks
}

When the package dies (see Gotchas), you swap the base class and every resource keeps working. Skip this and removing the dependency becomes a mechanical edit across every resource that ever used it.

Dispatch, don't do. Callbacks run inside the Nova HTTP request, after the DB write, with no transaction and no retry. The rule to enforce in code review: a callback may write to the same database or dispatch() a queued job. Nothing else.

public function afterCreate(NovaRequest $request, $model): void
{
    SyncContactToCrm::dispatch($model->id)->afterCommit();
    Mail::to($model->accountManager)->queue(new RecordCreated($model));
}

->afterCommit() matters. Depending on how your Nova version wraps the write, an inline queued job can be picked up by a worker before the transaction commits and read stale data. If you only remember one line from this assessment, make it that one.

Make every callback idempotent. Users retry on 500s and duplicate POSTs happen. Use ShouldBeUnique jobs, an upsert on a natural key, or a Cache::lock keyed on model + action.

Route shared behavior through a trait, not copy-paste. "Stamp updated_by and write an audit row" applies to most resources. One trait with the callback body, mixed into resources that opt in, beats duplicating it forty times.

Test callbacks through the Nova HTTP layer, never by calling the method directly. A unit test of afterUpdate() proves nothing about whether the package invokes it. Use Nova's testing helpers or actingAs plus a request to /nova-api/..., and add one canary test per resource that has a callback — that canary is what fails loudly when you bump Nova.

Use it to validate, then decide. The honest pattern: spike one workflow end-to-end with this package to confirm the product requirement is real and the shape is right, then reimplement as a trait you own. Callbacks are ~100 lines of store()/update() wrapping. You are renting ergonomics, not capability.

Gotchas and Tips

Bulk paths bypass it. Nova Actions, Lenses, and the import card do not go through resource store()/update(). Records created via "import CSV" or a mass action will silently have no callback side effects. This is the highest-probability source of "why is this row different?" bugs in production. Write a test for your import path specifically.

The post-commit failure window is real. Save succeeds → callback throws → user sees a 500 → user hits save again → side effect runs twice. Mitigate by keeping callback bodies cheap and idempotent, wrapping external calls in try/catch with Log::error(), and dispatching jobs for anything that can fail. Do not let a Slack webhook 500 the admin's save.

There are no logging or retry hooks in the package. If a callback dies, the only trace is an app exception. Wrap bodies yourself or, better, keep the body to a single dispatch() — the queue gives you retries and a failed_jobs record for free.

Version-pin two dimensions. Nova major × Laravel major. Treat every Nova upgrade as a breaking change for this package: upgrade, then run your canary tests. Add that line to the upgrade runbook with a named owner — an unowned dependency in the admin write path is the actual risk here, not the code.

Watch for method-override collisions. Other Nova extensions (redirect-after-save, tabs, action-style packages) may also wrap store()/update(). Ordering is whichever trait bootstraps first, which is not something you want to debug from a support ticket. If several such packages touch one resource, test the combination explicitly.

Check the Nova changelog first. Newer Nova versions may ship lifecycle hooks that make this redundant. Adopting a dormant package to duplicate a first-party feature is the worst outcome — verify before you composer require.

Soft deletes and forceDelete. Confirm from source whether the delete callback fires on soft delete, force delete, or both, and whether $model is still readable at that point. Do not assume.

Don't put business rules here. Anything an API endpoint, artisan command, or queue worker must also trigger belongs in a model observer or domain event. Rule of thumb: if a non-admin code path creates or mutates the record, the callback is the wrong home.

Have an exit plan from day one. Twenty stars, zero dependents, no release since June 2022, single maintainer. Plan for fork-and-vendor: copy the source into app/Support/, own it, delete the composer requirement. The realistic end state is inlining on the next Nova major — schedule it rather than discovering it mid-upgrade.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle