Extending MEXAR without forking: the plugin system
Customization should not mean a fork you can never upgrade. Here is how MEXAR plugins add routes, controllers, and migrations as self-contained packages that live outside the core.
Every operator eventually needs something the core does not do out of the box — a bespoke report, an integration with a local system, a workflow specific to their license. The wrong way to deliver that is to edit the core: you get a fork that fights every upgrade. MEXAR’s answer is a plugin system that lets customizations live alongside the platform instead of inside it.
A plugin is a self-contained package
Each plugin is an isolated package with its own routes, controllers,
configuration, and migrations — structured like a small Laravel package and kept
in a top-level Plugins/ directory, completely separate from the core app/:
Plugins/
└── MyPlugin/
├── plugin.json # metadata + status
└── src/
├── MyPluginServiceProvider.php
├── Http/Controllers/
├── routes/api.php
├── config/
└── Database/Migrations/
A plugin.json manifest declares the essentials — name, the service provider(s)
to register, a PSR-4 autoload mapping, and a status of active or inactive
that controls whether the plugin boots at all. New plugins scaffold as
inactive, so nothing runs until someone deliberately turns it on.
Discovered, then booted — predictably
Plugins are not scanned on every request. A discovery command
(app:plugin:discover) walks the directory and writes a cached manifest, and
the application boots from that. For each enabled plugin, the boot sequence is
explicit: resolve its path, check dependencies, register PSR-4 autoloading,
register the service provider, then load its routes, migrations, and configs.
The manifest deliberately stores relative paths (Plugins/MyPlugin) and
resolves them at boot, so the same manifest works across a developer’s laptop and
a Docker container without rewriting. Plugins can also declare what they depend
on:
{
"requires": {
"core": "10.0.0",
"plugins": { "AnotherPlugin": "1.0.0" }
}
}
If a required core version or sibling plugin is missing, the plugin simply does not boot and the reason is logged — a missing dependency degrades cleanly instead of crashing the platform.
Migrations that install and uninstall cleanly
The detail that makes plugins safe in production is that they do not touch core
migration history. Plugin migrations are tracked in their own
plugin_migrations table, so a plugin can be installed and removed
independently:
php artisan app:plugin:install MyPlugin # run this plugin's migrations
php artisan app:plugin:uninstall MyPlugin # roll them back, in reverse
To keep that isolation honest, plugin tables are namespaced by the plugin’s name
(my_plugin_items, not a generic items), so two plugins — or a plugin and the
core — can never collide on a table name.
Consistent on the outside
A plugin extends the platform, but it should not feel like a bolt-on to whoever
calls it. So plugin APIs follow the same conventions as the core: routes are
prefixed api/v1/plugins/{plugin-name}/, sit behind the standard auth:api
middleware, extend the same ApiController, return through the same response
helpers, and document with the same Scribe annotations. A consumer cannot tell a
plugin endpoint from a core one — which is the point.
Why this is the right shape for customization
This is the technical expression of how MEXAR is meant to be adopted: license the core, then tailor it — without that tailoring becoming a maintenance trap.
- Upgrade-safe — customizations sit outside
app/, so core upgrades don’t collide with them. - Reversible — independent migration tracking means a plugin installs and uninstalls cleanly.
- Isolated — namespaced tables and dependency checks keep plugins from interfering with each other or the core.
- Native — plugin endpoints follow core conventions, so they’re consistent to build against and to consume.
It is the same principle that runs through the platform — provider modules swap by contract, domains stay decoupled behind events, and customizations attach as plugins. Extend the edges; leave the core intact.