Notifal Lite ships page, post, product, post type, and WooCommerce cart display rules. Notifal Pro adds categories, URL conditions, and user targeting by hooking into extension points in the base plugin.
Follow the same two-layer pattern Pro uses:
- Admin UI , render fields on
notifal_display_rules_*section actions (seedisplay-rules-settings.php). - Runtime logic , register
ONPAGE_DISPLAY_RULES_*filters (seeDisplayRulesServiceandProDisplayRulesSecurityService).
Hook references:
Section action hooks (admin UI)
The notification editor Display Rules tab calls these actions inside the rule builder modal:
| Action | Used by Pro for |
|---|---|
notifal_display_rules_categories_section | Category archive targeting |
notifal_display_rules_url_match_section | URL pattern conditions |
notifal_display_rules_users_section | Login status and visit history |
Pro wires them in ProDisplayRulesIntegrationService::register():
add_action( 'notifal_display_rules_categories_section', [ $renderer, 'renderCategoriesSection' ], 10 );
add_action( 'notifal_display_rules_url_match_section', [ $renderer, 'renderUrlMatchSection' ], 10 );
add_action( 'notifal_display_rules_users_section', [ $renderer, 'renderUsersSection' ], 10 );
Render markup inside a .notifal-display-condition-section container so the existing Display Rules JavaScript can show and hide sections by rule type.
Filter hooks (data and evaluation)
Constants live in FilterHooks:
| Constant | Hook slug | Purpose |
|---|---|---|
ONPAGE_DISPLAY_RULES_SUPPORTED_TYPES | notifal/onpage/display_rules/supported_types | Register rule type metadata for the admin dropdown |
ONPAGE_DISPLAY_RULES_BEFORE_VALIDATION | notifal/onpage/display_rules/before_validation | Adjust rules before save or evaluation |
ONPAGE_DISPLAY_RULES_EVALUATION_RESULT | notifal/onpage/display_rules/evaluation_result | Override or extend server-side rule matching |
ONPAGE_DISPLAY_RULES_SANITIZED_SETTINGS | notifal/onpage/display_rules/sanitized_settings | Modify sanitized rules after save processing |
DisplayRulesService::shouldDisplay() applies BEFORE_VALIDATION, then short-circuits when EVALUATION_RESULT returns a non-null boolean.
Pro registers all three evaluation-related filters in ProDisplayRulesSecurityService::registerHooks().
Example: custom "referrer" rule type
This minimal integration adds a new rule type referrer, admin fields on a dedicated section hook, and server-side evaluation.
1. Bootstrap class
<?php
/**
* Plugin Name: My Notifal Referrer Display Rule
* Description: Adds a referrer-based display rule to Notifal notifications.
* Version: 1.0.0
* Requires PHP: 7.4
*/
defined( 'ABSPATH' ) || exit;
use MyNotifalReferrerRule\ReferrerDisplayRuleIntegration;
/**
* Boot integration after Notifal initializes.
*
* @return void
*/
add_action( 'notifal/initialized', function (): void {
// Instantiate and register hooks only when Notifal core is ready.
$integration = new ReferrerDisplayRuleIntegration();
$integration->register();
} );
2. Integration service
<?php
namespace MyNotifalReferrerRule;
defined( 'ABSPATH' ) || exit;
use Notifal\Infrastructure\WordPress\Hooks\FilterHooks;
use Notifal\Modules\OnPageNotification\Application\Services\Settings\DisplayRulesDataNormalizer;
/**
* Registers admin UI and evaluation hooks for the referrer display rule.
*/
class ReferrerDisplayRuleIntegration {
/**
* Rule type slug stored in _notifal_display_rules_data.
*/
private const RULE_TYPE = 'referrer';
/**
* Register WordPress hooks.
*
* @return void
*/
public function register(): void {
// Render admin fields when the referrer rule type is selected.
add_action( 'notifal_display_rules_url_match_section', [ $this, 'renderReferrerSection' ], 20 );
// Expose the rule type in the admin rule type list (Pro-active sites use the same filter).
add_filter( FilterHooks::ONPAGE_DISPLAY_RULES_SUPPORTED_TYPES, [ $this, 'addRuleType' ] );
// Evaluate referrer rules on the server during eligibility checks.
add_filter( FilterHooks::ONPAGE_DISPLAY_RULES_EVALUATION_RESULT, [ $this, 'evaluateReferrerRules' ], 10, 4 );
}
/**
* Add referrer rule metadata for the Display Rules UI.
*
* @param array<string, array<string, mixed>> $rule_types Existing rule types.
* @return array<string, array<string, mixed>>
*/
public function addRuleType( array $rule_types ): array {
// Append a custom rule definition (icon and label appear in the admin builder).
$rule_types[ self::RULE_TYPE ] = [
'label' => __( 'Referrer', 'my-notifal-referrer-rule' ),
'icon' => '🔗',
];
return $rule_types;
}
/**
* Output admin fields for the referrer rule (placed near URL conditions).
*
* @return void
*/
public function renderReferrerSection(): void {
?>
<div class="notifal-display-condition-section notifal-display-referrer notifal-hidden">
<label for="target_referrer_contains">
<?php esc_html_e( 'Referrer contains', 'my-notifal-referrer-rule' ); ?>
</label>
<input
type="text"
name="target_referrer_contains"
id="target_referrer_contains"
class="regular-text"
value=""
placeholder="<?php esc_attr_e( 'google.com', 'my-notifal-referrer-rule' ); ?>"
/>
<p class="description">
<?php esc_html_e( 'Show the notification when the HTTP referrer contains this string.', 'my-notifal-referrer-rule' ); ?>
</p>
</div>
<?php
}
/**
* Evaluate referrer rules when present in the saved rule set.
*
* @param bool|null $result Existing result (null = continue default logic).
* @param array<string, mixed> $rules Saved display rules container.
* @param string $combination_logic AND or OR combination mode.
* @param array<string, mixed> $context Evaluation context from DisplayRulesService.
* @return bool|null
*/
public function evaluateReferrerRules( $result, array $rules, string $combination_logic, array $context ) {
// Respect an earlier filter that already decided visibility.
if ( $result !== null ) {
return $result;
}
// Extract individual rule items from the normalized container shape.
$items = DisplayRulesDataNormalizer::extractItems( $rules );
// Track whether any referrer rule exists in this notification.
$has_referrer_rule = false;
// Collect boolean match results keyed by rule id.
$matches = [];
foreach ( $items as $item ) {
// Read the rule type slug from the saved item.
$type = isset( $item['type'] ) ? sanitize_key( (string) $item['type'] ) : '';
// Skip items that are not referrer rules.
if ( $type !== self::RULE_TYPE ) {
continue;
}
$has_referrer_rule = true;
// Read rule-specific data saved from the admin UI.
$data = isset( $item['data'] ) && is_array( $item['data'] ) ? $item['data'] : [];
$needle = isset( $data['referrer_contains'] ) ? sanitize_text_field( (string) $data['referrer_contains'] ) : '';
// Read the visitor referrer from context or the current request.
$referrer = isset( $context['http_referrer'] )
? sanitize_text_field( (string) $context['http_referrer'] )
: ( isset( $_SERVER['HTTP_REFERER'] ) ? sanitize_text_field( wp_unslash( (string) $_SERVER['HTTP_REFERER'] ) ) : '' );
// Empty needle matches all referrers (same as "any").
$matched = ( $needle === '' ) || ( $referrer !== '' && stripos( $referrer, $needle ) !== false );
$rule_id = isset( $item['id'] ) ? sanitize_key( (string) $item['id'] ) : self::RULE_TYPE;
$matches[ $rule_id ] = $matched;
}
// Defer to core logic when this notification has no referrer rules.
if ( ! $has_referrer_rule ) {
return null;
}
// Combine per-rule results using AND or OR semantics.
if ( $combination_logic === 'AND' ) {
$combined = ! in_array( false, $matches, true );
} else {
$combined = in_array( true, $matches, true );
}
// Honor show_if vs hide_if visibility mode when provided in context.
$visibility = isset( $context['visibilityMode'] )
? DisplayRulesDataNormalizer::sanitizeVisibilityMode( (string) $context['visibilityMode'] )
: DisplayRulesDataNormalizer::VISIBILITY_SHOW_IF;
if ( $visibility === DisplayRulesDataNormalizer::VISIBILITY_HIDE_IF ) {
return ! $combined;
}
return $combined;
}
}
Wire your admin JavaScript to map target_referrer_contains into rule data.referrer_contains with type referrer when saving, following the same shape Pro uses (id, type, data).
Sanitize custom rule data on save
Hook notifal/onpage/display_rules/sanitized_settings to normalize custom keys before they are stored in _notifal_display_rules_data:
<?php
defined( 'ABSPATH' ) || exit;
use Notifal\Infrastructure\WordPress\Hooks\FilterHooks;
use Notifal\Modules\OnPageNotification\Application\Services\Settings\DisplayRulesDataNormalizer;
add_filter(
FilterHooks::ONPAGE_DISPLAY_RULES_SANITIZED_SETTINGS,
function ( array $sanitized, array $settings ): array {
// Walk each saved rule item after core sanitization.
$items = DisplayRulesDataNormalizer::extractItems( $sanitized );
foreach ( $items as $index => $item ) {
// Only touch referrer rule items.
if ( ( $item['type'] ?? '' ) !== 'referrer' ) {
continue;
}
// Ensure data is an array before writing keys.
if ( ! isset( $items[ $index ]['data'] ) || ! is_array( $items[ $index ]['data'] ) ) {
$items[ $index ]['data'] = [];
}
// Sanitize the referrer substring stored in post meta.
$raw = $items[ $index ]['data']['referrer_contains'] ?? '';
$items[ $index ]['data']['referrer_contains'] = sanitize_text_field( (string) $raw );
}
// Re-wrap items into the container shape Notifal expects.
return DisplayRulesDataNormalizer::wrapItems( $items );
},
10,
2
);
How Notifal Pro maps to these hooks
| Pro class | Responsibility |
|---|---|
ProDisplayRulesIntegrationService | Registers the three notifal_display_rules_* section renderers |
ProDisplayRulesRendererService | Outputs category, URL, and user admin fields |
ProDisplayRulesSecurityService | Adds pro rule types, validates access, evaluates pro rules |
Pro adds rule types in addProRuleTypes() via ONPAGE_DISPLAY_RULES_SUPPORTED_TYPES. Evaluation for Pro rule types runs in evaluateProRules().
When extending display rules in your own plugin, return null from EVALUATION_RESULT when your rule type is not present so core Lite logic continues.
Client-side rules
Some rules (users visit history, cart totals, page targeting) are mirrored to the frontend via client_user_rules, client_cart_rules, and client_page_rules in NotificationDataPreparer. Server-only rules (like referrer on first paint) should be evaluated in NotificationEligibilityChecker through the filters above, or documented as client-side if you add matching frontend logic.
What to read next
Cookbook series
- Cookbook: Modify Notification Before Display
- Cookbook: Add Custom Dynamic Tag
- Cookbook: Customize Template Import
- Cookbook: Extend Display Rules (you are here)
- Cookbook: Register a Custom HTML Builder Widget