Feature Flags & getSupports()
The Features enum, getSupports(), and how core conditionally enables behavior.
Last updated: April 6, 2026
Feature Flags & getSupports()
Siren’s extension system uses feature flags to declare what capabilities each integration provides. Core uses these flags to conditionally enable engagement triggers, conversion types, metric strategies, and UI elements. This prevents the system from offering functionality that no active extension can fulfill.
What is the Features enum?
All feature flags are defined in Siren\Extensions\Core\Enums\Features:
<?php
namespace Siren\Extensions\Core\Enums;
use PHPNomad\Enum\Traits\Enum;
class Features
{
use Enum;
public const Coupons = 'coupons';
public const Courses = 'courses';
public const Lessons = 'lessons';
public const Posts = 'posts';
public const Renewals = 'renewals';
public const Forms = 'forms';
public const ManualOrdering = 'manual_ordering';
}
This is not a PHP enum. It is a class using the Enum trait with string constants. Reference flags with Features::Coupons, Features::Courses, etc.
How do I declare extension capabilities?
Every extension implements getSupports() from the Extension interface:
interface Extension extends Module
{
public function getName(): string;
public function getDescription(): string;
public function canActivate(): bool;
public function getIsActive(): bool;
/**
* Gets an array of Siren features that this extension supports.
*
* @return Features::*[]
*/
public function getSupports(): array;
}
Return an array of Features constants that your extension provides:
public function getSupports(): array
{
return [
Features::Coupons,
Features::ManualOrdering,
];
}
Dynamic Feature Declaration
Features can be conditional based on what is installed. WooCommerce, for example, only declares Renewals support when WooCommerce Subscriptions is installed:
// From WooCommerce Integration
public function getSupports(): array
{
$supports = [
Features::Coupons,
Features::ManualOrdering,
];
if (class_exists('WC_Subscriptions')) {
$supports[] = Features::Renewals;
}
return $supports;
}
What do the individual feature flags mean?
The commerce-related flags cover the most common attribution scenarios. Features::Coupons lets Siren track coupon codes tied to collaborators as engagements, enabling the BoundCouponUsed trigger and metric strategies. Features::ManualOrdering extends that commerce support to admin-created orders, so manually placed orders can still be attributed. Features::Renewals goes further by tracking subscription renewal payments. Each renewal can fire a new conversion and generate additional commissions for the referring collaborator. Since renewals depend on a subscriptions plugin, WooCommerce only declares this flag when WooCommerce Subscriptions is active.
LMS integrations use two closely related flags. Features::Courses tracks course completions, firing the CourseCompleted engagement trigger when a student finishes a course. Features::Lessons does the same at a finer granularity with LessonCompleted, tracking individual lesson completions. An extension can declare both to support attribution at either level, or just one depending on what the LMS platform exposes.
The remaining flags cover content and lead generation. Features::Posts enables post-based attribution (Essentials tier). When a visitor reads a post bound to a collaborator, Siren tracks the engagement through the BoundPostUsed trigger strategy. Features::Forms serves a similar role for form submissions, enabling the CollaboratorFormSubmitted trigger and metric strategies so that lead capture can be attributed alongside content engagement. See the integration feature matrix for a full comparison of which features each built-in integration declares.
How does Siren’s core use feature flags?
The core system checks feature flags to conditionally register engagement triggers, conversion types, and metric strategies. This prevents the UI from showing options that no installed extension can handle.
How do I check feature support at runtime?
Feature checking goes through ExtensionRegistryService::extensionsSupportFeatures():
class ExtensionRegistryService implements ExtensionStorageService
{
/**
* Checks whether any active extensions support all specified features.
*
* @param string $feature The first required feature.
* @param string ...$features Additional required features.
* @return bool True if at least one active extension supports all specified features.
*/
public function extensionsSupportFeatures(string $feature, string ...$features): bool
{
return !empty($this->query(
(new ExtensionQueryBuilder())->isActive()->supports($feature, ...$features)
));
}
}
This method returns true if at least one active extension supports all of the specified features.
Feature Checks in Engagement Trigger Registration
The most visible use of feature flags is in engagement trigger registration listeners. Here is the core registration:
class RegisterCoreEngagementTriggerStrategies implements CanHandle
{
protected ExtensionRegistryService $extensionRegistryService;
public function __construct(ExtensionRegistryService $extensionRegistryService)
{
$this->extensionRegistryService = $extensionRegistryService;
}
public function handle(Event $event): void
{
if ($event instanceof EngagementTriggerRegistryInitiated) {
// Always available -- no feature flag needed
$event->addStrategy(ReferredSiteVisit::class);
$event->addStrategy(Manual::class);
// Only available if an active extension supports Coupons
$this->addStrategyIfSupported($event, BoundCouponUsed::class, Features::Coupons);
}
}
protected function addStrategyIfSupported(
EngagementTriggerRegistryInitiated $event,
string $strategy,
string $feature,
string ...$features
) {
if ($this->extensionRegistryService->extensionsSupportFeatures($feature, ...$features)) {
$event->addStrategy($strategy);
}
}
}
The Essentials tier adds more feature-gated strategies:
class RegisterEssentialsEngagementTriggerStrategies implements CanHandle
{
public function handle(Event $event): void
{
if ($event instanceof EngagementTriggerRegistryInitiated) {
$this->addStrategyIfSupported($event, BoundPostUsed::class, Features::Posts);
$event->addStrategy(CollaboratorProductSold::class);
$this->addStrategyIfSupported($event, CollaboratorFormSubmitted::class, Features::Forms);
$this->addStrategyIfSupported($event, CourseCompleted::class, Features::Courses);
$this->addStrategyIfSupported($event, LessonCompleted::class, Features::Lessons);
}
}
}
Feature Checks in Metric Trigger Registration
The same pattern appears for distribution metrics:
class RegisterCoreMetricTriggerStrategies implements CanHandle
{
public function handle(Event $event): void
{
if ($event instanceof MetricTypeRegistryInitiated) {
// Always available
$event->addStrategy(ReferredSiteVisit::class);
$event->addStrategy(BoundPostUsed::class);
$event->addStrategy(CollaboratorProductSold::class);
$event->addStrategy(Manual::class);
// Feature-gated
$this->addStrategyIfSupported($event, CollaboratorFormSubmitted::class, Features::Forms);
$this->addStrategyIfSupported($event, BoundCouponUsed::class, Features::Coupons);
$this->addStrategyIfSupported($event, CourseCompleted::class, Features::Courses);
$this->addStrategyIfSupported($event, LessonCompleted::class, Features::Lessons);
}
}
}
How do I query extensions with complex conditions?
For more complex queries beyond simple feature support checks, use ExtensionQueryBuilder:
$extensions = $extensionRegistryService->query(
(new ExtensionQueryBuilder())
->isActive()
->supports(Features::Coupons)
);
// Other query methods:
$query = (new ExtensionQueryBuilder())
->isActive() // Only active extensions
->isInactive() // Only inactive extensions
->supports('coupons') // Must support this feature
->doesNotSupport('courses') // Must NOT support this feature
;
How do I add feature-conditional logic in my extension?
Checking Features at Runtime
If your extension needs to conditionally behave based on what other extensions support, inject ExtensionRegistryService:
class MyListener implements CanHandle
{
protected ExtensionRegistryService $extensions;
public function __construct(ExtensionRegistryService $extensions)
{
$this->extensions = $extensions;
}
public function handle(Event $event): void
{
if ($this->extensions->extensionsSupportFeatures(Features::Coupons)) {
// Coupon-related logic
}
if ($this->extensions->extensionsSupportFeatures(Features::Courses, Features::Lessons)) {
// Only if BOTH courses AND lessons are supported
}
}
}
Declaring Your Own Features
If your extension provides a capability that core should know about, add the feature to your getSupports() return. The feature constants in Features are the currently recognized set, but the system uses strings internally, so custom features are possible if you also write the listeners that check for them.
Conditional Event Bindings
WooCommerce demonstrates conditional event bindings based on available features:
public function getEventBindings(): array
{
$triggers = [
SaleTriggered::class => [
['action' => 'woocommerce_new_order', 'transformer' => $saleTransformerCallback],
],
];
// Only add renewal bindings if WC Subscriptions is available
if (class_exists(WC_Subscription::class)) {
$triggers[RenewalTriggered::class] = [
['action' => 'woocommerce_subscription_renewal_payment_complete',
'transformer' => $renewalTransformerCallback],
];
}
return $triggers;
}
How does the feature flag lifecycle work?
When an extension loads, canActivate() checks whether the third-party plugin is present. If it passes, load() runs and the extension becomes active. At that point getSupports() declares its capabilities. During registry events, Siren’s core listeners call extensionsSupportFeatures() to decide what strategies to register, and only strategies whose feature requirements are met appear in the system. The program creation UI then reflects only the engagement types, conversion types, and metrics that have registered strategies.
The practical effect is that installing WooCommerce automatically enables coupon-based engagement tracking, renewal conversion types, and manual ordering support. Uninstalling it removes those options. No configuration needed.