Guide

Architecture

Mantle is a modular plugin with an MVC-ish PHP core and a React admin application. This section describes the boot sequence, the module system, and the conventions that hold it together.

In this section

  • Admin UI — routing, layouts, and how views are rendered.

Boot sequence

Flow

From plugin load to ready

5 relationships
Modules initialise before controllers so capabilities exist before roles are touched.

The ordering is deliberate. initialize_modules() runs first because capabilities must be registered before anything assigns them to roles.

  1. Constants and autoloaders

    mantle.php defines constants, loads vendor/autoload.php, de-shadows core’s PHP AI Client, then loads the PHP-Scoper-prefixed third-party/vendor/autoload.php.

  2. Bootstrap

    On plugins_loaded, mantle_init() instantiates Core\Bootstrap and calls run(). If the class is missing it renders an admin notice instead of fataling.

  3. Modules

    Core\Modules hooks register_modules() and register_settings() onto init, then Module_Loader::init() registers schemas for all modules and initialises the enabled ones.

  4. Controllers

    load_controllers() globs includes/Controller/*.php recursively and instantiates each concrete class, then calls register_actions() where present.

  5. CLI and completion

    When WP_CLI is defined, load_cli_controllers() globs includes/CLI/. Finally mantle_done fires.

Controller auto-discovery

Bootstrap::load_controllers() is convention-driven rather than a registry. Understanding its filters explains why some classes load and others do not.

Reference

Controller discovery rules

Applied in Bootstrap::load_controllers().
5 fields
Path pattern string
Files under includes/Controller/ matching /Controller/(.+)\.php, excluding names ending _Interface or _Module.
Skipped subdirectories string
Any class whose resolved name contains Agents\, Contracts\, CLI\, or Modules\ is skipped — those are initialised by their owners instead.
Conditional skip string
GravityForms controllers are skipped unless the GFForms class exists.
Reflection guards string
Abstract classes and interfaces are skipped via ReflectionClass before instantiation.
Failure handling string
Instantiation is wrapped in try/catch; a class that throws is skipped silently.

Module system

Flow

Module registration

6 relationships
External plugins hook the same action core uses, so third-party modules are first-class.

Three guards make registration idempotent: Module_Loader::init() returns early once initialised, Core\Modules::register_modules() guards on a static flag, and Module_Loader::register() ignores an already-registered ID.

The split between register_schema() and init() is the system's central design decision:

Reference

register_schema versus init

2 fields
register_schema() string
Registers the settings filter and the capabilities filter. Runs for every module regardless of state, so stored settings are never orphaned and the capability reference stays complete.
init() string
Adds hooks, registers REST controllers, and adds the admin submenu. Runs only when is_enabled() returns true.

Directory layout

mantle/ File tree
  • mantle.php
  • includes/
    • Core/
      • Bootstrap.php
      • Modules.php
      • Setup.php
      • View.php
      • LicenseManager.php
    • Controller/
      • Admin.php
      • REST/
    • Model/
      • Settings.php
      • Abilities/
      • Users/
    • Modules/
      • Abstract_Module.php
      • Module_Loader.php
      • Module_Interface.php
    • Helper/
    • CLI/
    • Shared/Interfaces/
  • src/
    • constants/
    • components/
    • layouts/
    • modules/
    • slots/
    • stores/
    • views/
  • build/
  • third-party/
  • tests/
  • docs/
PSR-4 maps the Mantle namespace to includes/. build/ and third-party/vendor/ are generated.
Reference

Configuration fields

Typed options, defaults, and constraints in one scannable reference.
3 fields
site string required
WordPress site domain or numeric site ID.
status string
Publication status for synchronized Pages.
dryRun boolean
Preview reconciliation without writing changes.

Dependency isolation

Mantle ships two autoloaders. Ordinary dependencies load from vendor/; dependencies likely to collide with other plugins are rewritten by PHP-Scoper into a private namespace under third-party/.

For the AI Client specifically, WordPress 7.0 bundles its own copy in core. When the plugin also has one — for instance because dev dependencies were installed — mantle.php calls setPsr4( 'WordPress\AiClient\', [] ) on the Composer loader to unregister the plugin's prefix, so core's copy is the only one in play.

Understanding the codebase quickly

Prompt Claude Opus 5 Ask Thinking
Map the Mantle plugin's boot sequence. Start at mantle.php, follow Core\Bootstrap::run(), and produce an ordered list of every hook Mantle adds during boot with its callback and priority. Flag any hook added from inside another hook's callback, since those can register too late to fire.
@repository mantle.php includes/Core/Bootstrap.php includes/Core/Modules.php includes/Modules/Module_Loader.php
Orientation prompt for the boot sequence

See also

Was this helpful?