Start here
1
Scaffold it
--type takes service, gateway, provider or addon. Leave it off and the command asks.2
Switch it on
3
Check it
TODO to find what’s left to fill in.
The generator runs
composer dump-autoload for you and leaves the module disabled, which is
the safe default. Read Enabling a module before you use
--enable, because php-fpm caches the autoloader and there is a timing trap.Which type do I want?
Service modules
Something people buy that you have to create: a game server, VPS, hosting account, domain or
licence key.
Payment gateways
Taking payments through a new provider. Core already knows how to turn a payment into an invoice
or a service.
OAuth providers
Sign in with Discord, Google, GitHub and friends. The smallest module type.
Addon modules
Anything else: affiliates, blogs, forms, compliance tooling, status pages.
The other pages
Fundamentals
The parts every module shares: manifest, providers, routes, permissions, settings UI,
credentials, events. Read it once and you can read any module in the tree.
Conventions reference
Exact class names, method signatures, what calls what. Keep it open while you work.
Five things that will bite you
Class names are the contract
Class names are the contract
Core finds your code by building a string like
Modules\AcmePanel\Services\RenewService. Spell
it wrong and nothing happens. No error, no warning, the feature just doesn’t exist.php artisan module:doctor is how you catch that.Run composer dump-autoload after adding a class
Run composer dump-autoload after adding a class
Some modules autoload through a classmap rather than PSR-4, and classmaps are static. Your new
file stays invisible until you dump.Anything from
make:module lives under app/, so PSR-4 covers it and you won’t hit this.Never swallow a provisioning failure
Never swallow a provisioning failure
If the customer has paid and the server didn’t get created, throw an exception. Throwing gets
the queued job retried. Returning
['success' => false] and logging a warning means nobody finds
out until they complain.This is the most expensive class of bug in the product.Don't add the encrypted cast to a column that already has data
Don't add the encrypted cast to a column that already has data
The existing credential models encrypt with
Crypt::encryptString(). Laravel’s encrypted cast
uses Crypt::encrypt(), which serialises. They are not compatible, so adding the cast makes
every stored credential unreadable.New columns are fine. See Credentials.Check the app still works when your module is disabled
Check the app still works when your module is disabled
Meaning: check the rest of the app does. Nothing in core should depend on your tables, listeners
or routes.That’s the step people skip and the one that breaks production.

