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

Plugin Hook Laravel Package

catchadmin/plugin-hook

A lightweight hook system for CatchAdmin that lets plugins register and listen for events, so you can extend core behaviour without patching framework code. Useful for inserting custom logic at defined extension points in a Laravel-based admin panel.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require catchadmin/plugin-hook

The package is small and Laravel-native: on Laravel 9+/10 it should be picked up by package auto-discovery through extra.laravel.providers. On older skeletons, or if you've vendored it as a path repository, add the service provider to config/app.php manually. Check for a publishable config with php artisan vendor:publish --provider="CatchAdmin\PluginHook\...ServiceProvider" — if nothing is published, the defaults are fine.

Where to look first. This package carries almost no documentation and zero dependents, so the source is the manual. Open, in order:

  1. composer.json — PHP and illuminate/* constraints, so you don't fight your framework version.
  2. src/ — the service provider (what it binds into the container) and the hook manager class (the entire public API: add/listen, fire/apply, remove, priority).
  3. Any facade or helper file — that's what you'll actually type in application code.
  4. config/ — the default registry, if one ships.

Expect the API surface to be a WordPress-shaped dispatcher: register a callback against a named string, fire it later, optionally pass a value through and take the return value back. Confirm the exact method names before writing production code — the assessment below uses Hook::addFilter / Hook::applyFilters / Hook::addAction / Hook::doAction as the conventional shape.

First use case. Don't retrofit the whole app. Pick one seam where core code currently hard-codes a list, and invert it.

// app/Http/Controllers/DashboardController.php
$widgets = Hook::applyFilters('dashboard.widgets', $defaultWidgets);

return view('dashboard', compact('widgets'));

Then let a plugin — or just another module in your own app — contribute:

// packages/acme/analytics/src/Providers/AnalyticsServiceProvider.php
public function boot(): void
{
    Hook::addFilter('dashboard.widgets', function (array $widgets) {
        $widgets[] = new AnalyticsWidget();

        return $widgets;
    }, priority: 20);
}

That's the whole value proposition: downstream code extends upstream code without a composer.json fork.

Implementation Patterns

Separate actions from filters, rigorously. Actions are fire-and-forget side effects (Hook::doAction('order.paid', $order)). Filters transform and must return (Hook::applyFilters('invoice.number', $number)). Mixing them is how hook systems rot — a listener that mutates by side effect inside a filter chain is invisible to everyone downstream.

Namespace every hook you define. dashboard.widgets collides the moment a second plugin uses the same obvious name. Use vendor.package.point (acme.analytics.dashboard.widgets). You own the namespace for hooks you emit; plugins own theirs for hooks they consume.

Keep hook registration declarative in boot(). Register the callback, don't do work. A closure that queries the database at registration time will run on every request, including route:list and queue workers booting.

Use priority deliberately, and document it. Filters resolve in priority order (typically low number = earlier). If two plugins both append to a widget list and order matters, that ordering is now load-bearing production behavior. Write it down next to the hook declaration.

Wrap the emission point, not the call sites. Emit hooks from a single service or base controller, so you can add caching, logging, or a try/catch in one place later.

Model plugin lifecycle as hooks too. plugin.enabled, plugin.installed, plugin.uninstalling give you a place to purge cache, drop tables, or migrate data — much cleaner than reaching into plugin classes from your admin controller.

Test the seam directly. In feature tests, register a spy listener, hit the route, assert the value arrived. Give yourself a Hook::flush()-style reset (or rebuild the application) between tests, otherwise callbacks leak across test cases and produce order-dependent failures.

White-labeling is the killer app. One codebase, N clients: filters on brand name, menu entries, PDF templates, and notification channels. Far cheaper than branches.

Know when to stop. Hooks are synchronous, in-process, and stringly-typed. For domain events that need queues, retries, or multiple subscribers across services, use Laravel's native events. Reach for this package when you specifically need ordered, value-transforming, synchronous extension inside one request.

Gotchas and Tips

  • Boot order is not guaranteed. Callback order across plugins depends on provider registration order and discovery ordering. If a listener needs to run before another, encode it in priority — never in composer.json order.
  • Filters that don't return break the chain. A forgotten return yields null, and the next listener receives null. This fails silently. Consider a debug mode where the dispatcher asserts a non-null return when the input was non-null.
  • Static registry + long-lived workers. Under Octane, Swoole, or a queue worker that boots once, the hook registry persists across requests. If anything registers or removes hooks at runtime (per-tenant, per-request), you will leak between requests. Register only at boot.
  • Config caching. If hooks or plugin lists come from config, php artisan config:cache in deploy will freeze them. Run config:clear in your deploy script or avoid config-driven registration.
  • Listener exceptions abort the chain. Unless the dispatcher wraps calls, one bad plugin callback takes down the page. Wrap third-party-facing emissions in try/catch and log with the hook name attached — you'll want that name in Sentry.
  • No removal path, no kill switch. Verify there's a remove/forget method. Without one, disabling a plugin at runtime requires unregistering at boot. If it's missing, keep a per-hook enable flag in your own config and check it inside your callback.
  • Zero stars, zero dependents, thin docs. Pin the version exactly ("catchadmin/plugin-hook": "1.x.y"), and read the diff before every bump. This is a package extracted from CatchAdmin for internal use; the public API may not be treated as stable.
  • Debugging. When "the hook isn't firing," the answer is almost always a name typo, a listener registered in a provider that didn't boot, or a plugin registered after the emission point. Add a temporary Hook::getListeners('your.hook') dump (or whatever introspection the manager exposes) at the emission site, and log the resolved name at both ends.
  • Extend by binding. If the manager is resolved from the container, you can subclass it and bind your own implementation to add logging, metrics, or a dashboard of registered hooks — a genuinely useful admin panel feature for plugin ecosystems.
  • The real risk isn't the code, it's the missing ecosystem. You are the first integrator. Budget an afternoon to read src/ end to end and write your own one-page README with the method signatures you actually use. Future-you and every teammate will thank you.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings