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. Octane workers hold the autoloader in memory, and there’s 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, and the rules for running under Octane.
Conventions reference
Exact class names, method signatures, what calls what. Keep it open while you work.
Installing a module
Got a module from the marketplace or a colleague? Where to put it and what to run.
Running under Octane
Why your change isn’t live yet, and the code patterns that leak between requests.
Six 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.Nothing is live until Octane reloads
Nothing is live until Octane reloads
Octane boots the app once per worker and reuses it for thousands of requests. Routes, providers,
the module list and every PHP class stay in memory until the workers reload, so an edit you just
saved isn’t running yet.The same goes for anything that runs in a queue worker (provisioning, queued listeners): run
php artisan queue:restart too.And because a worker outlives the request, state you put in a static property or a singleton
leaks to the next customer. See
Running under Octane.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, then reload Octane.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.

