Creating a Module
This guide builds a working module for the Flavor Starter Theme using Module System 2.0. A module is
a single PHP class that extends Flavor\Modules\AbstractModule and is registered on the
flavor_register_modules action.
Read the Module System Overview first. The key idea: metadata lives on
the class as static methods — there is no metadata array and no procedural module.php entry file.
Step 1 — Create the module directory
Put your module in a child theme (recommended) or a companion plugin:
flavor-starter-child/
└── modules/
└── sale-banner/
├── SaleBannerModule.php # filename MUST end in "Module.php"
└── assets/
└── sale-banner.css
Step 2 — Write the module class
Extend AbstractModule and implement the five required static methods. Register your hooks in the
constructor (or in boot() — both run when the module is active). This example renders a dismissible
banner above the site header and stores its message + colour as settings.
<?php
namespace MyVendor\FlavorModules\SaleBanner;
use Flavor\Modules\AbstractModule;
defined('ABSPATH') || exit;
class SaleBannerModule extends AbstractModule
{
// ── Required metadata ──────────────────────────────────── ─────
public static function id(): string { return 'sale-banner'; }
public static function version(): string { return '1.0.0'; } // strict semver X.Y.Z
public static function category(): string { return 'general'; }
public static function icon(): string { return 'megaphone'; } // dashicon slug
public static function name(): string
{
return __('Sale Banner', 'flavor');
}
public static function description(): string
{
return __('Shows a promotional banner above the header', 'flavor');
}
// ── This is a third-party module ──────────────────────────────
public static function external(): bool { return true; }
/** Single option that holds this module's settings (array). */
private string $settingsKey = 'module_sale_banner_settings';
// ── Register hooks (runs only when the module is active) ───────
public function __construct()
{
add_action('flavor_header_before', [$this, 'render']);
add_action('wp_enqueue_scripts', [$this, 'enqueueAssets']);
}
private function getSettings(): array
{
$saved = flavor_get_option($this->settingsKey, []);
return array_merge([
'message' => __('Summer sale — 20% off everything!', 'flavor'),
'bg_color' => '#ef4444',
], is_array($saved) ? $saved : []);
}
public function render(): void
{
$s = $this->getSettings();
printf(
'<div class="sale-banner" style="--sale-bg:%s">%s</div>',
esc_attr($s['bg_color']),
esc_html($s['message'])
);
}
public function enqueueAssets(): void
{
wp_enqueue_style(
'sale-banner',
get_stylesheet_directory_uri() . '/modules/sale-banner/assets/sale-banner.css',
[],
self::version()
);
}
}
Notes on this example:
external()returnstrue. Without it the ModuleManager will not track your on/off state, so the module can never be switched on. Every third-party module needs this.- Hooks are registered in the constructor. The manager only instantiates active, licensed modules, so
the constructor is a safe place to wire hooks.
boot()works identically if you prefer it. flavor_header_beforeis a real theme action (others:flavor_header_start,flavor_header_end,flavor_header_after). See the Theme Hooks reference.- Settings are stored as one array option and read with
flavor_get_option($key, []). See the Settings API.
Step 3 — Register the module
In your child theme's functions.php (or your plugin's bootstrap), hook flavor_register_modules and
call register() with the class name — not an array. Because your class lives outside the theme's
Flavor\Modules\ namespace, you must require_once its file yourself first:
add_action('flavor_register_modules', function ($manager) {
require_once __DIR__ . '/modules/sale-banner/SaleBannerModule.php';
$manager->register(\MyVendor\FlavorModules\SaleBanner\SaleBannerModule::class);
});
register() validates the class before adding it: it must exist, extend AbstractModule, use a strict
X.Y.Z version, and pass the PHP / WordPress / requiresPlugin() compatibility gate. If any check fails
the module is skipped (and, outside a product-update window, a _doing_it_wrong() notice is raised) — it
will not fatal your site.
Step 4 — Enable and test
- Go to Appearance → Flavor Options → Modules.
- Your module appears in the list — switch it on.
- Reload the storefront and confirm the feature works.
- Open Flavor Modules (top-level admin menu) to see it in the active-modules Overview, and check the unified Debug viewer for any warnings (see Best Practices).
If your module does not appear or won't stay enabled, check the three most common causes:
| Symptom | Cause |
|---|---|
| Module never appears | Class file not named *Module.php, or require_once missing before register(). |
| Appears but can't be enabled | external() doesn't return true. |
| Registered but never runs | Host license invalid and requiresParentLicense() is true (the default). |
| Silently rejected | version() isn't strict semver X.Y.Z, or a requires*() gate failed. |
Required vs. optional methods (quick reference)
Required (abstract — must implement): id(), version(), category(), name(), description().
Commonly overridden: icon(), requiresPlugin(), external(), requiresParentLicense(),
requiresPhp(), requiresWp(), plus the lifecycle methods boot(), activate(), deactivate(),
uninstall() and the admin hook getAdminMenuConfig().
Full list + defaults: Module System Overview.
Learn from the built-in modules
The shipped modules are the canonical reference for real-world structure:
inc/modules/general/icons/IconsModule.php— a minimal General module: metadata, a constructor that registers a shortcode, and an admin page viagetAdminMenuConfig().inc/modules/eshops/stock-alerts/StockAlertsModule.php— a plugin-dependent module (requiresPlugin()→true): a custom table created oninit, AJAX handlers, front-end assets, an admin settings screen, and a settings array.
Copy their shape; adjust the metadata and hooks to your feature.