Custom Conversion Types
Registering new conversion types that define what measurable outcomes your extension tracks.
Last updated: April 6, 2026
Custom Conversion Types
Conversion types represent the categories of measurable outcomes that Siren tracks. Each conversion type defines a kind of customer action (a sale, a lead capture, a subscription renewal) that can trigger rewards for collaborators. Conversion types are the bridge between what happened (the customer action) and how it gets rewarded (the incentive type).
What defines a conversion type?
namespace Siren\Conversions\Core\Models;
use PHPNomad\Datastore\Interfaces\DataModel;
use PHPNomad\Datastore\Interfaces\HasSingleStringIdentity;
class ConversionType implements DataModel, HasSingleStringIdentity
{
/**
* @param string $id Unique identifier (e.g., 'sale', 'lead')
* @param string $label Plural display label (e.g., 'Sales', 'Leads')
* @param string $singularLabel Singular display label (e.g., 'Sale', 'Lead')
* @param string[] $supportedIncentives Incentive type IDs that can process this conversion type
*/
public function __construct(
string $id,
string $label,
string $singularLabel,
array $supportedIncentives = []
);
public function getId(): string;
public function getLabel(): string;
public function getSingularLabel(): string;
/** @return string[] */
public function getSupportedIncentives(): array;
/**
* Check if this conversion type supports a specific incentive type.
*/
public function supportsIncentive(string $incentiveTypeId): bool;
}
The supportedIncentives Array
The supportedIncentives array is the key mechanism that links conversion types to incentive types. It contains the string IDs of incentive types that know how to calculate rewards for this kind of conversion.
For example, a “sale” conversion supports saleFixedPerProduct, saleFixedPerTransaction, and saleTransactionPercentage incentive types. A “lead” conversion only supports leadFixed. This prevents invalid configurations. You cannot assign a percentage-of-sale incentive to a lead conversion because leads have no transaction amount.
Which conversion types ship out of the box?
The sale conversion type is always registered and is the fundamental type for commerce. It supports three incentive types: saleFixedPerProduct, saleFixedPerTransaction, and saleTransactionPercentage.
The Essentials tier adds two more. The lead conversion type represents non-monetary actions like form submissions or account signups, and only supports the leadFixed incentive type. The renewal conversion type represents subscription renewal payments and supports the same three sale-based incentive types as sale. Renewal is only registered when the active commerce extension supports the Renewals feature.
Registration Pattern
Conversion types are registered by listening for ConversionTypeRegistryInitiated and calling addConversionType():
namespace Siren\Conversions\Core\Listeners;
use PHPNomad\Events\Interfaces\CanHandle;
use PHPNomad\Events\Interfaces\Event;
use Siren\Conversions\Core\Events\ConversionTypeRegistryInitiated;
use Siren\Conversions\Core\Models\ConversionType;
class RegisterCoreConversionTypes implements CanHandle
{
public function handle(Event $event): void
{
$event->addConversionType('sale', fn() => new ConversionType(
'sale',
'Sales',
'Sale',
[
'saleFixedPerProduct',
'saleFixedPerTransaction',
'saleTransactionPercentage'
]
));
}
}
The addConversionType() method takes a string key and a callable factory:
public function addConversionType(string $field, callable $resolver): void
{
$this->registry->set($field, $resolver);
}
Wire your listener to the event in your Initializer:
public function getListeners(): array
{
return [
ConversionTypeRegistryInitiated::class => RegisterCoreConversionTypes::class,
];
}
Conditional Registration
Conversion types that depend on platform features can be registered conditionally using the ExtensionRegistryService:
protected function addConversionTypeIfSupported(
ConversionTypeRegistryInitiated $event,
string $id,
string $label,
string $singularLabel,
array $supportedIncentives,
string $feature,
string ...$features
): void {
if ($this->extensionRegistryService->extensionsSupportFeatures($feature, ...$features)) {
$event->addConversionType($id, fn() => new ConversionType(
$id, $label, $singularLabel, $supportedIncentives
));
}
}
This pattern ensures that conversion types only appear in the UI when the required commerce integration is active.
How do I look up conversion types at runtime?
The ConversionTypeProvider service provides access to registered conversion types at runtime:
namespace Siren\Conversions\Service\Datastores;
class ConversionTypeProvider
{
/** Get a single conversion type by ID. */
public function getConversionTypeFromId(string $id): ?ConversionType;
/** Get all registered conversion type IDs. */
public function getConversionTypeIdentifiers(): array;
/** Get all registered ConversionType instances. */
public function getConversionTypes(): array;
/** Get conversion types that support a specific incentive type. */
public function getConversionTypesForIncentive(string $incentiveTypeId): array;
}
The getConversionTypesForIncentive() method is particularly useful for building UI that shows only the valid conversion types for a chosen incentive type.
Creating a Custom Conversion Type
When to Create One
Create a custom conversion type when you have a measurable customer outcome that does not fit into sales, leads, or renewals. Examples:
A trial signup (a user starts a free trial that isn’t yet a sale), a course enrollment, a booked appointment, or an app install are all examples of outcomes that don’t fit the built-in sale, lead, or renewal types.
Example: Trial Signup
namespace MyPlugin\Listeners;
use PHPNomad\Events\Interfaces\CanHandle;
use PHPNomad\Events\Interfaces\Event;
use Siren\Conversions\Core\Events\ConversionTypeRegistryInitiated;
use Siren\Conversions\Core\Models\ConversionType;
class RegisterTrialConversionType implements CanHandle
{
public function handle(Event $event): void
{
if (!$event instanceof ConversionTypeRegistryInitiated) {
return;
}
$event->addConversionType('trial', fn() => new ConversionType(
'trial',
'Trial Signups',
'Trial Signup',
['trialFixed'] // Your custom incentive type ID
));
}
}
Pairing With a Custom Incentive Type
A custom conversion type usually requires a corresponding custom incentive type. The supportedIncentives array must contain the ID of at least one incentive type that knows how to create conversions and obligations for this kind of event.
The full integration requires four pieces. You need a conversion type registered via ConversionTypeRegistryInitiated to define the outcome category, and a corresponding incentive type registered via IncentiveRegistryInitiated to define how to calculate the reward. You also need a triggering event and listener — the platform event that fires when the customer action occurs (like a TrialStarted event) — and a conversion initializer listener that fires ConversionInitialized when that triggering event occurs.
Removing or Replacing a Built-in Type
Use deleteConversionType() on the registry event:
public function handle(Event $event): void
{
if ($event instanceof ConversionTypeRegistryInitiated) {
// Remove the default sale type
$event->deleteConversionType('sale');
// Add a customized version
$event->addConversionType('sale', fn() => new ConversionType(
'sale',
'Purchases', // Different label
'Purchase',
['saleTransactionPercentage'] // Fewer supported incentives
));
}
}
How Conversion Types Fit the Pipeline
Customer Action -> Event -> Conversion Initializer -> ConversionInitialized
-> BuildConversions (checks ConversionType + Incentive compatibility)
-> Conversion Created -> Obligation -> Payout
When BuildConversions processes a ConversionInitialized event, it checks whether the program’s incentive type is listed in the conversion type’s supportedIncentives. If the incentive type is not supported, no conversion is created for that program. This is the enforcement mechanism that prevents misconfigured programs from generating invalid conversions.