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

Laravel Package Toolkit Laravel Package

nyoncode/laravel-package-toolkit

Laravel Package Toolkit streamlines building Laravel packages with an expressive config API that auto-handles routes, migrations, views, translations, commands, and components, plus lifecycle hooks, middleware, events, and publishing options.

View on GitHub
Deep Wiki
Context7

Getting Started

This is not a package you install into a normal application — it is a toolkit for authoring packages. You reach for it the day you decide to extract a chunk of an app into vendor/yourname/your-package, or when you start a fresh package and don't want to hand-write six loadViewsFrom/loadMigrationsFrom/mergeConfigFrom blocks.

composer require nyoncode/laravel-package-toolkit

The whole integration surface is one class. Your package's service provider extends PackageServiceProvider instead of Illuminate\Support\ServiceProvider, implements Packable, and describes itself in configure():

class MyPackageServiceProvider extends PackageServiceProvider implements Packable
{
    public function configure(Packager $packager): void
    {
        $packager
            ->name('My Awesome Package')
            ->hasConfig()
            ->hasRoutes()
            ->hasMigrations()
            ->hasTranslations()
            ->hasViews();
    }
}

That is the first use case and it should take five minutes: hasX() with no arguments assumes the conventional directory (config/, routes/, database/migrations/, lang/, resources/views/), so an already-convention-shaped skeleton just works. Register the provider in your package's composer.json extra.laravel.providers as usual.

Where to look first:

  1. ai/AGENTS.md in the installed package (vendor/nyoncode/laravel-package-toolkit/ai/AGENTS.md) — the complete public fluent API extracted from source. This is the fastest reference, faster than the site.
  2. The docs site (package-toolkit.nyoncode.cz), one page per resource type. /llms.txt and /llms-full.txt exist if you'd rather feed it to an agent.
  3. Check your Laravel version before anything else — see Gotchas.

If you use a coding agent, run vendor/bin/package-toolkit-ai install once. It drops the API reference, a Claude Code skill, and a zero-dependency MCP server into the project. That is genuinely the differentiator here: the agent reads the installed source rather than hallucinating a stale API.

Implementation Patterns

Fluent declaration, then hooks. Keep configure() purely declarative and put behaviour in the lifecycle hooks. You have two equivalent spellings and should pick one per package:

// override methods on the provider
public function registeringPackage(): void { /* ... */ }
public function bootingPackage(): void { /* ... */ }
public function registeredPackage(): void { /* ... */ }
public function bootedPackage(): void { /* ... */ }

// or chain them inside configure()
$packager->registeringPackage(fn ($p) => Log::info('registering'))
         ->bootedPackage(fn ($p) => Log::info('booted'));

The hook order is fixed: before register(), after register(), before boot(), after boot(). Singletons go in registeredPackage(); anything touching routes, events, or views goes in bootingPackage().

Conditional resources without if spaghetti. The when* family is the most day-to-day useful thing in the package. A package that ships dev-only commands and a local config shouldn't force every consumer to guard them:

$packager
    ->hasRoutes(['api.php'])
    ->whenLocal(fn ($p) => $p->hasConfig('local-config.php')->hasCommands())
    ->whenProduction(fn ($p) => $p->hasConfig('production-config.php'))
    ->whenConsole(fn ($p) => $p->hasCommands())
    ->whenExtensionLoaded('redis', fn ($p) => $p->hasConfig('redis-config.php'));

whenEnvironment([...]) takes an array; whenClassExists() and whenExtensionLoaded() are how you make optional integrations (Redis, a payment SDK) not blow up in consumers who don't have them.

Custom paths when the package isn't conventional. Every hasX() takes either a list of filenames or a directory: named argument, and paths may be relative to the package root ('../www/routes/web.php'). Use the named directory: form for a whole tree, and the array form for a subset of files inside the default tree.

Infrastructure registrations belong in the packager too. Rather than dropping into boot():

$packager
    ->hasMiddlewareAliases(['custom.alias' => CustomMiddleware::class])
    ->hasMiddlewareGroups(['web' => [WebMiddleware::class]])
    ->hasMiddlewareGlobals([SecurityMiddleware::class])
    ->hasEvents([OrderPlaced::class => [SendConfirmation::class, LogOrder::class]])
    ->hasSubscribers([UserEventSubscriber::class])
    ->hasOptimizeCommands(optimize: 'my-package:cache', clear: 'my-package:clear');

hasOptimizeCommands forwards to Laravel's ServiceProvider::optimizes(), so your cache-warmup command runs on php artisan optimize and clears on optimize:clear — easy to forget, and a nice win over hand-wiring.

Broadcast channels are a separate axis from routes. Channel authorization callbacks go in hasBroadcastChannels(['channels.php'], directory: '../broadcasting'), not hasRoutes(). Registration silently no-ops when illuminate/broadcasting isn't installed, which makes it safe in packages that only optionally broadcast.

Testing. The reference suite runs Pest against Orchestra Testbench, so no separate app/DB. composer test, composer lint (PHPStan/Larastan level 7 over src only), composer pint. composer build / composer serve bring up the workbench app for manual poking — that's the fastest loop when a resource isn't loading and you can't tell why.

Gotchas and Tips

The version floor is the first landmine. v2.1 dropped Laravel 10 and 11 entirely; you need 12.x >= 12.61.1 or 13.x >= 13.12.0. Those minimums aren't arbitrary — they're the first releases patched for the June 2026 advisories (including CVE-2026-48019, CRLF injection). If your consumers are still on 10/11, pin ~2.0.0; Laravel 9 means ^1.0. This is the single most likely reason a composer require fails in a real project, so check it before you build anything on top.

hasRoutes() is not a dumping ground. It loads files into the router inside a route group. Anything that is not a route definition — channel auth callbacks especially — will load without error and simply never fire correctly. Same instinct applies to console-only files.

Channel files are deliberately not publishable. An application doesn't load routes/channels.php unless its own bootstrap asks for it, so a published copy would look authoritative while the package kept using its own. Consumers override authorization by re-registering the same channel name — last registration wins. Don't fight this; document it.

Short names are validated eagerly. hasShortName() enforces kebab-case, lowercase letters/numbers/hyphens only. If you skip it, the short name is derived from name() — so ->name('My Awesome Package') gives you my-awesome-package, which is probably not the config/translation namespace you wanted. Set it explicitly on day one; renaming later means rewriting every __('my-awesome-package::...') and config('my-awesome-package.…') call.

Everything validates at declaration time. The builder throws on bad input rather than failing silently at boot, which is good — but it means a typo'd directory: surfaces during composer install/provider registration, not at request time. Read the stack trace top-down; the exception names the resource.

Config merging vs. publishing is two different mechanisms. hasConfig() gives you mergeConfigFrom (consumer overrides work out of the box). Publishing a config file is a separate, opt-in step via the install command / publishing options. Don't assume a consumer's published config is what the package is reading — it is, but only because merge wins in the right order, and it won't if they edit vendor/ directly.

Adoption risk is real. Zero stars, zero dependents, and it's a fresh package (releases dated into late 2026). It's MIT and the source is right there, but you are an early adopter: expect the API to move, and don't make it a hard dependency of a package you plan to keep supporting for years without a fallback. The escape hatch is friendly, at least — every hasX() maps to a handful of standard ServiceProvider calls, so unwinding later is mechanical.

Keep the AI artifacts in sync if you fork or vendor it. The ai/ directory ships with the package and is what agents in consuming projects read out of vendor/. If you patch the toolkit locally, run node ai/mcp/server.mjs --self-test — it parses src/ and docs/ and prints a summary, so a layout change that silently breaks the MCP answers shows up immediately.

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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator