Extending with Custom Modules
6 min read
The TrustLens detection module system is designed to be extensible. Beyond the eight built-in modules, developers can write their own modules that observe custom events, emit signals into the trust score, and respect the standard enable/disable toggle. This page walks through building a custom module from scratch.
The Module Interface #
Every module extends the abstract TrustLens_Module class:
abstract class TrustLens_Module {
protected $id;
protected $name;
protected $is_pro = false;
abstract public function register_hooks();
abstract public function get_signal( string $email_hash ): array;
public function get_settings_fields(): array {
return array();
}
public function is_enabled(): bool {
return (bool) get_option( "trustlens_module_{$this->id}_enabled", true );
}
}
The required methods:
register_hooks()— wire the module into WordPress/WooCommerce eventsget_signal($email_hash)— return the module’s current signal contribution for a customer
Optional:
get_settings_fields()— declare settings UIis_enabled()— override if you need custom enable logic
Example: Subscription Churn Risk Module #
Let’s build a custom module that flags customers as risky when they cancel subscriptions soon after signup. The full code:
<?php
/**
* Subscription Churn Risk Module
*
* Flags customers who cancel subscriptions within 30 days of signup.
*/
defined( 'ABSPATH' ) || exit;
class My_Module_Subscription_Churn extends TrustLens_Module {
protected $id = 'subscription_churn';
protected $name = 'Subscription Churn Risk';
public function register_hooks() {
add_action(
'woocommerce_subscription_status_cancelled',
array( $this, 'handle_cancellation' ),
10, 1
);
}
public function handle_cancellation( $subscription ) {
if ( ! $subscription ) {
return;
}
$start_date = $subscription->get_date( 'start' );
$cancelled_date = $subscription->get_date( 'cancelled' );
$days_active = ( strtotime( $cancelled_date ) - strtotime( $start_date ) ) / DAY_IN_SECONDS;
if ( $days_active > 30 ) {
return; // Not "early" cancellation
}
$email = $subscription->get_billing_email();
$email_hash = wstl_get_email_hash( $email );
wstl_log_event( $email_hash, 'subscription_early_cancel', array(
'subscription_id' => $subscription->get_id(),
'days_active' => (int) $days_active,
) );
// Increment a counter (stored as custom meta)
// Persist count by encoding in an event row and reading back on demand.
// The events table is the canonical place for module-specific history.
global $wpdb;
$count = (int) $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}trustlens_events
WHERE email_hash = %s AND event_type = %s",
$email_hash,
'subscription_early_cancel'
) );
// Event was just logged above, so the count is current.
wstl_queue_score_update( $email_hash );
}
public function get_signal( string $email_hash ): array {
global $wpdb;
$count = (int) $wpdb->get_var( $wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}trustlens_events
WHERE email_hash = %s AND event_type = %s",
$email_hash,
'subscription_early_cancel'
) );
if ( $count >= 3 ) {
return array(
'score' => -20,
'reason' => sprintf( '%d subscription early cancellations', $count ),
);
}
if ( $count >= 2 ) {
return array(
'score' => -10,
'reason' => 'Multiple subscription early cancellations',
);
}
if ( $count === 1 ) {
return array(
'score' => -5,
'reason' => 'Subscription early cancellation',
);
}
return array( 'score' => 0, 'reason' => '' );
}
public function get_settings_fields(): array {
return array(
array(
'id' => 'subscription_churn_window',
'title' => 'Early Cancellation Window (days)',
'type' => 'number',
'default' => 30,
'desc' => 'Days after start within which cancellation counts as "early"',
),
);
}
}
Registering the Module #
Hook into the trustlens/register_modules action:
add_action( 'trustlens/register_modules', function( $module_manager ) {
if ( ! class_exists( 'WC_Subscriptions' ) ) {
return; // Only register if Subscriptions is active
}
$module_manager->register( new My_Module_Subscription_Churn() );
} );
The module is registered before plugin init completes. After registration:
- It appears in the Modules settings tab with its own enable toggle
- It contributes signals to customer scores
- Its signals appear in customer profiles tagged with its module ID
- It can be referenced in automation rules (Pro)
Helper Functions Available #
| Function | Use |
|---|---|
wstl_get_email_hash($email) |
Compute the keyed HMAC-SHA256 hash for a customer email |
wstl_get_customer($email_hash) |
Get customer record object |
wstl_log_event($hash, $type, $data) |
Append to event timeline |
wstl_queue_score_update($hash) |
Queue async score recalculation (deduped) |
apply_filters( 'trustlens/is_pro_active', false ) |
Check whether Pro features are unlocked |
Storing Module-Specific Data #
Three options for storing data your module needs:
1. The Events Table (Recommended) #
Write module observations to {prefix}trustlens_events via wstl_log_event(). Read counts back at signal time with a simple SELECT COUNT(*). This pattern fits TrustLens’s existing append-only event model and benefits from the existing email_hash + event_type indexes.
2. WordPress Options (Lightweight Per-Store State) #
For settings or store-wide flags your module needs, use the WordPress options API: get_option() / update_option() with a my_module_* prefix.
3. Custom Table (Complex Multi-Row State) #
For complex per-customer data that doesn’t fit the events shape (e.g. composite aggregates), create your own table. Follow WordPress conventions: prefix with {wpdb->prefix}my_module_, use dbDelta() for creation.
There is no built-in per-customer meta table in TrustLens. The events table plus aggregate columns on trustlens_customers cover the patterns the core modules use; mirror that approach in your own modules.
Settings UI Integration #
Modules with get_settings_fields() automatically get a settings sub-section in Settings → Modules. The fields render as standard WordPress settings fields.
Reading a setting in your module:
$window_days = (int) get_option( 'trustlens_module_subscription_churn_subscription_churn_window', 30 );
Module Lifecycle #
- register_hooks(): Called once during init, only if the module is enabled. Wire up your hooks here.
- get_signal(): Called each time a customer’s score is recalculated. Read state, return signal.
- get_settings_fields(): Called when the settings page renders. Return field definitions.
Modules are reinstantiated on every page load (Action Scheduler workers, admin pages, REST requests). Don’t rely on instance state — store everything in the database.
Best Practices #
- Use the
module_idconsistently. It appears in signal logs, automation rule context, and admin UIs. - Don’t fire score recalculations synchronously. Always use
wstl_queue_score_update(). - Handle missing dependencies gracefully. If your module relies on another plugin (Subscriptions, Memberships, etc.), check before registering.
- Don’t store raw PII. Use hashes consistent with TrustLens’s existing scheme.
- Respect the enabled toggle. The base class handles this — don’t bypass
is_enabled(). - Use Action Scheduler for batch operations. Don’t loop over thousands of records synchronously.
Testing Your Module #
- Activate the module via Modules settings
- Trigger a relevant event (e.g. cancel a test subscription)
- Verify the event appears in the customer’s timeline
- Wait 1–2 minutes for score recalculation
- Open the customer’s profile and confirm the signal appears in the breakdown
For automated testing, use the WordPress test suite with mock objects.
Distributing Your Module #
Custom modules can be:
- Single-purpose plugins (recommended) — separate plugin that depends on TrustLens being active
- Code in your theme’s
functions.php— fine for one-off store customizations - Mu-plugins — useful for required customizations across a site
If distributing publicly, follow WordPress plugin guidelines and declare dependency on TrustLens via the Requires Plugins header.
Examples of Custom Modules #
- Subscription churn (above)
- External fraud API integration — call an external service, factor result into score
- Loyalty program tier signal — boost score for active loyalty members
- B2B order velocity — different scoring for B2B vs B2C customers
- Promotion participation tracking — flag customers who only buy during sales
- Customer lifetime value scoring — explicit LTV signal