Skip to main content

Addon Registration & Gating

This page traces how StoreEngine goes from a slug in a filter to a booted, gated addon: the loader map, the autoloader, the run() call, the active-status option, and the checks and APIs that decide whether an addon's code actually executes. It's the reference behind Create your first addon and Addon architecture.

The registration pipeline

Everything starts in includes/addons.php. Addons::init() constructs the registry and calls addons_loader(), which builds the slug → class-name map behind a filter:

$addons = apply_filters( 'storeengine/addons/loader_args', [
'paypal' => 'Paypal',
'stripe' => 'Stripe',
'affiliate' => 'Affiliate',
'subscription' => 'Subscription',
'webhooks' => 'Webhooks',
'seo' => 'Seo',
// ...many more core addons...
'funnel-builder' => 'FunnelBuilder',
] );

Then for each entry it does three things:

foreach ( $addons as $addon_name => $addon_class_name ) {
$addon_root_path = STOREENGINE_ADDONS_DIR_PATH . $addon_name . '/';
$addon_namespace = 'StoreEngine\\Addons\\' . $addon_class_name;

// 1. Register the addon's namespace with the autoloader.
Autoload::get_instance()->add_namespace_directory( $addon_namespace, $addon_root_path );

// 2. Instantiate the singleton and run its lifecycle.
$class = $addon_namespace . '\\' . $addon_class_name;
$class = $class::init();
$class->run();

// 3. Collect active addons for schema syncing.
if ( Helper::get_addon_active_status( $addon_name ) ) {
$this->loaded_addons[ $addon_name ] = $class;
}
}

add_action( 'init', [ $this, 'sync_addon_schemas' ], 4 );
do_action( 'storeengine/addons/loaded' );
  1. Autoloader wiring. The namespace StoreEngine\Addons\<Class> is mapped to STOREENGINE_ADDONS_DIR_PATH . '<slug>/', so the addon's classes resolve on demand.
  2. init()->run(). The singleton is created and run() executes the lifecycle (constants, activation wiring, gate, init_addon()). See Architecture.
  3. Active collection. Only addons that pass the gate are stored in loaded_addons, which feeds schema syncing.

After the loop, a single init hook (priority 4) drives table syncing for every active addon, and storeengine/addons/loaded fires.

The loader_args filters

Two filters register addons, one per plugin:

FilterRegistersNamespace built
storeengine/addons/loader_argsCore (free) addonsStoreEngine\Addons\<Class>
storeengine_pro/addons/loader_argsPro addonsStoreEnginePro\Addons\<Class>

To register an addon from your own plugin, hook the appropriate filter:

add_filter( 'storeengine/addons/loader_args', function ( array $addons ): array {
$addons['my-feature'] = 'MyFeature';
return $addons;
} );
Autoload path for external addons

The core loader points the autoloader at STOREENGINE_ADDONS_DIR_PATH . $slug . '/'. If your addon class lives outside StoreEngine's addons/ directory, register its namespace with your own autoloader (or Autoload::get_instance()->add_namespace_directory()) so it can be found — registering the slug alone isn't enough when the file lives elsewhere.

Active status: the storeengine_addons option

Whether an addon is enabled is separate from whether it is registered. Active status lives in the storeengine_addons option (constant STOREENGINE_ADDONS_SETTINGS_NAME, defined in storeengine.php as 'storeengine_addons'), stored as a JSON object of slug => boolean and hydrated into the global $storeengine_addons on load:

$GLOBALS['storeengine_addons'] = json_decode( get_option( STOREENGINE_ADDONS_SETTINGS_NAME, '{}' ) );

The gate: get_addon_active_status()

run() calls one function to decide whether to boot an addon, and you use the same one to check active status from your own code. From includes/utils/helper.php:

public static function get_addon_active_status( $addon_name, $is_pro = false ): bool {
global $storeengine_addons;
if ( $is_pro && ! self::is_active_storeengine_pro() ) {
return false;
}
if ( isset( $storeengine_addons->{$addon_name} ) ) {
return (bool) $storeengine_addons->{$addon_name};
}

return false;
}
  • First argument — the addon slug. Returns the boolean from $storeengine_addons, or false if the slug isn't present.
  • $is_pro (second argument) — when true, the check also requires StoreEngine Pro to be active (is_active_storeengine_pro()). Use this for Pro addons so they gate on both their own toggle and the Pro plugin being present.

Checking from your own code

use StoreEngine\Utils\Helper;

// Is a core addon enabled?
if ( Helper::get_addon_active_status( 'affiliate' ) ) {
// Affiliate features are on.
}

// Is a Pro addon enabled AND is Pro active?
if ( Helper::get_addon_active_status( 'inventory-pro', true ) ) {
// Safe to call the Pro addon's APIs.
}

This is the canonical way to feature-detect another addon before calling into it.

Activate / deactivate API

Addons::activate() and Addons::deactivate() flip an addon's status, persist the option, and fire the matching lifecycle hook.

\StoreEngine\Addons::activate( 'my-feature' );
\StoreEngine\Addons::deactivate( 'my-feature' );

Both:

  • Read (or lazily load) the $storeengine_addons global.
  • Return a WP_Error if the addon is already in the requested state (addon_already_activated / addon_already_deactivated).
  • Set $storeengine_addons->{$slug} to true/false.
  • Persist via update_option( STOREENGINE_ADDONS_SETTINGS_NAME, wp_json_encode( $storeengine_addons ) ).
  • Fire storeengine/addons/activated_{$slug} or storeengine/addons/deactivated_{$slug}.

Those dynamic hooks are what AbstractAddon::run() wired to your addon_activation_hook() / addon_deactivation_hook(), so activating an addon runs its setup even though init_addon() didn't run earlier in that request.

Fired hookBound toTypical use
storeengine/addons/activated_{slug}addon_activation_hook()Seed defaults, create tables, flush rewrites.
storeengine/addons/deactivated_{slug}addon_deactivation_hook()Teardown that should happen on disable.

The admin toggle (AJAX)

The admin Addons screen toggles addons through two AJAX actions in includes/ajax/addons.php, both capability-gated to manage_options:

ActionPurpose
get_all_addonsReturns the current storeengine_addons map for the UI.
saved_addon_statusEnables/disables an addon after running guard checks.

saved_addon_status does more than flip a bit. Before persisting it:

  • Enforces any required host plugin (required_plugin) declared for the addon and errors if it isn't active.
  • On enable, blocks if the addon has missing sibling requirements (get_missing_requirements()).
  • On disable, blocks if active dependents still rely on it (get_active_dependents()).
  • Fires the appropriate activated_/deactivated_ hook, then writes the option.

The dependency logic is covered in Dependencies.

Pro loader differences

The Pro plugin (storeengine-pro/includes/addons.php) mirrors the core loader but:

  • Reads its map from storeengine_pro/addons/loader_args.
  • Builds namespaces under StoreEnginePro\Addons\.
  • Reuses core's schema manager — it calls \StoreEngine\Addons::sync_schema_for( $this->loaded_addons ) (guarded by method_exists for version-skew safety) so Pro addon schemas are tracked in the same storeengine_addons_db_version option.