Skip to content
Back to blog
Engineering June 14, 2026 · By MEXAR Engineering

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.