Skip to main content
Addons are the freest type. Core has no conventions it looks for inside one, so you’re writing a normal Laravel and Inertia feature that happens to live in Modules/Addons/.
Read Fundamentals first if you haven’t.

Generate it

The generated credentials model assumes you talk to an external API. If you don’t, delete it and its migration, and swap the settings controller over to a key-value settings table instead.

How addons differ

Everything happens through four things: routes you register, listeners on core events, Inertia pages in register.ts, and your own tables.

Hooking into core

Events are the main integration point. Register them in EventServiceProvider:
The full list is in Fundamentals.
Two things to get right there: keep configureEmailVerification() as a no-op, and never list a listener that already lives in app/Listeners.
Queue your listeners:
An addon that throws inside a synchronous listener can break checkout for everyone.
Affiliates is the best reference here. It listens to registration, payment and renewal, and does payouts off the back of them.

Settings

Keep the generated SettingsController and just change the fields. You get a full admin page with no React. See the settings section.

User-facing routes

routes/web.php
Admin routes and their permissions are already set up by the generator. See Admin permissions if you need to add more.
There’s no manifest hook for sidebar links. They come from the SidebarLink table, which admins manage in the admin panel.
So either seed a row when your module is enabled, which is friendlier, or document the URL and let the admin add it:

Commands and cron

Use moduleSchedule() or routes/console.php, not both.

Touching core data

Adding a column to users or services is fine. Make it nullable and the migration idempotent:
Never change or drop a core column, and don’t edit App\Models\User. Point a relationship at it from your own model instead:
If you need more than one field, prefer a side table with a foreign key. It keeps your module removable.

Test it

1

Enable it

Migrations run, defaults seed, admin page loads.
2

Trigger each event you listen to

Check the side effects.
3

Disable it, and confirm the app still works completely

Nothing in core should depend on your tables, listeners or routes.
4

Re-enable it

No duplicate rows, no failed migrations.
Step 3 is the one people skip and the one that breaks production.

Worth reading

LoyaltyProgram

Small and event-driven.

News

Content CRUD with admin and public pages.

Affiliates

Listeners, payouts, a console command, 4 React pages.

Forms

The most frontend-heavy, 12 pages.
Affiliates, Blog, KnowledgeBase and News still use an older hand-written service provider. Copy their features, not their providers.