tomatophp/laravel-package-generator
A Laravel package generator that scaffolds new packages in seconds, creating service providers, config files, routes, migrations, and the full directory structure. It automates boilerplate so developers can focus on building reusable, publishable Laravel packages faster.
Install it where it belongs: a scratch app or a global Composer home — never a package you'll publish.
# Option A: throwaway host app
laravel new scaffold-scratch && cd scaffold-scratch
composer require --dev tomatophp/laravel-package-generator
# Option B: global, keeps every app's lockfile clean
composer global require tomatophp/laravel-package-generator
Auto-discovery registers the command inside a host Laravel app; with a global install, confirm it resolves in the target project (php artisan list | grep -i package). Pin an exact version (:1.0.5 or a commit SHA) — 8 stars and a single maintainer means silent stub drift will land on you.
Read the source before you run anything. There is no docs site; the docs are the package:
vendor/tomatophp/laravel-package-generator/
├── src/Console/ # the generate command: prompts, options, defaults, hardcoded bits
├── stubs/ # every single file that lands on disk — this is the artifact you're buying
└── config/ # publishable config, if present
Open the command class first to see which prompts exist (vendor, package name, namespace, Filament, API routes, translations, assets) and which are baked in. Then read stubs/ line by line — that diff is your entire evaluation.
First use case: generate one representative package and push it through your real CI unmodified.
php artisan <generate-command> # answer vendor/namespace prompts
cd packages/<vendor>/<name>
composer validate --strict
composer install
vendor/bin/pint --test
vendor/bin/phpstan analyse
vendor/bin/pest # or phpunit / orchestra/testbench
If that unmodified run is green or nearly green, the generator saves you real hours. If it needs heavy repair before it passes, stop and treat the generated output as a specification for an internal template instead — you've already extracted the value.
Wire the output into a path-repository monorepo. Generated packages land inert until Composer can see them. In the host app's composer.json:
{
"repositories": [
{ "type": "path", "url": "packages/*/*", "options": { "symlink": true } }
]
}
Then composer update <vendor>/<name> so it's symlinked and autoloadable. This one step is the difference between "the generator works" and "the generator is broken" in most developers' first five minutes.
Wrap the raw command in an internal make:package script. The wizard's output will not match your conventions, and hand-editing four files on every generation is the failure mode. The wrapper runs the generator, then applies your standards in one pass: license headers, PHP/Laravel version floors in composer.json, Pint/PHPStan/Rector configs, a CI workflow, README/CHANGELOG templates, and .github/. That wrapper — not the vendor command — is what goes in onboarding docs.
Fork the stubs rather than patching output. If the same edits recur, the stub is wrong. Fork the repo, edit stubs/, and pin it in the host app:
{ "type": "vcs", "url": "[email protected]:your-org/laravel-package-generator.git" }
Because output is ordinary Laravel code, abandonment degrades cleanly to "we maintain our fork." Budget a couple of hours per Laravel major to refresh idioms.
Commit the scaffold and your edits as two separate commits. Immediately after generation:
git init && git add -A && git commit -m "chore: scaffold from generator v1.0.5"
# ... your human edits ...
git commit -m "feat: real implementation"
Reviewers see exactly what the tool produced versus what you changed — and you get a running list of stub fixes for the fork.
Prototype package boundaries before splitting the monolith. The cheap-package property is the real payoff: generate a package, move one module into it, wire the path repository, and see whether the boundary holds under tests. If it doesn't, delete the directory — a few minutes lost instead of a sprint.
Do the human pass on the service provider every time. Check publish tags, loadMigrationsFrom, loadViewsFrom, loadTranslationsFrom, and that the PSR-4 namespace in composer.json matches the namespace registered in the provider. Generated packages are 80% done, not 100%.
Prune the TomatoPHP/Filament bias if you're not on that stack. Strip unused Filament/Splade/other opinionated deps from the generated composer.json before you publish — a lean dependency graph is a review requirement at most orgs, and consumers inherit whatever you leave behind.
Generate several in one sitting when standardizing. Standing up three or four packages back to back is the fastest way to surface name-conversion bugs, migration-timestamp collisions, and publish-tag clashes — the defects that only appear at scale.
Vendor name and PHP namespace are separate prompts. acme/audit-log with namespace Acme\AuditLogs is the most common post-generation repair. Decide the mapping rule up front and let your fork enforce it.
Hyphen-to-StudlyCase will surprise you. Multi-segment names (acme/laravel-audit-log) can produce Acme\LaravelAuditLog or Acme\AuditLog depending on stub logic. Generate one deliberately awkward name during evaluation and grep every namespace and use.
"Class not found" right after generation is Composer, not the generator. A new packages/ directory needs both the repositories entry and composer update <vendor>/<name>. Check that before filing anything.
Case sensitivity only fails in CI. If a stub writes src/Models/ while the namespace says model, it's fine on macOS and broken on Linux. Always run generated output through CI, not just locally.
Migration timestamps collide. Generate multiple packages the same day and their migrations can share a timestamp, which surfaces as silently missing tables in test suites. Verify loadMigrationsFrom is present and bump timestamps manually when they clash.
Publish tags are global. Two packages both exposing --tag=config makes vendor:publish nondeterministic. Namespace tags in the provider (audit-log-config) and add that to the wrapper's checklist.
View and translation namespaces fail at render time, not boot. If the provider registers loadViewsFrom(..., 'audit-log'), code must call view('audit-log::index'). Mismatches surface as "view not found" in production-like rendering paths — assert them in a test.
Expect Laravel-major drift. Last release 2024-10-26 means the stubs are frozen around whatever major the maintainer targeted. Stale idioms (old $namespace property, pre-casts() model stubs, removed helpers) are the most likely defect and are cheap to fix in your fork.
Keep it out of anything you publish. Not in require, not in the generated package's require-dev. It's a workstation/CI tool; leaking it into a consumer's dependency graph is a reviewer's veto.
Debug by reading, not guessing. No docs, no community, no dependents. src/Console/ and stubs/ are definitive. If the output is wrong, the stub is wrong — fix upstream in your fork, not in each generated package.
Watch for over-scaffolding. The cheapest possible cost of package creation is package sprawl the platform team then supports. Pair the generator with one sanctioned stub set and a lightweight conformance check in package CI (namespace policy, version floor, static-analysis level, license header).
Zero dependents = zero external validation. No downstream consumers are catching regressions, and issue response is one person's best effort. Name an internal owner before rollout — when a scaffolded package misbehaves, the complaint lands on the tool, not the maintainer.
Don't document it until one pilot package ships a real release. A generator that looks right after composer install can still be wrong about tagging, changelogs, and CI on the second commit. One release cycle of evidence before it enters onboarding docs.
How can I help you explore Laravel packages today?