Feature Checking

Use the client library's feature and tier methods to conditionally enable functionality based on the customer's license. Features are defined per pricing tier on the server and checked locally using cached validation data.

Methods reference

Method Returns Description
is_active() bool Returns true if the license is valid and currently active. Uses cached validation data -- does not make an API call on every invocation.
has_feature( $slug ) bool Checks whether the current tier's feature array includes the specified slug. Returns false if the license is inactive or the feature is not present.
get_features() array Returns all feature slugs available on the current tier. Returns an empty array if the license is inactive.
get_tier_label() string|null Returns the display name of the current tier (e.g. "Pro", "Enterprise"). Returns null if no tier data is available.
is_trial() bool Returns true if the license has trial status.
get_trial_end() string|null Returns an ISO 8601 date string for the trial end date (e.g. 2026-04-15T00:00:00Z), or null if the license is not a trial.
get_trial_days_remaining() int Returns the number of full days remaining in the trial period. Returns 0 if the license is not a trial or the trial has expired.

Note

All feature-checking methods rely on the cached validation response. The cache is refreshed according to the cache_duration setting (3600 seconds for the 3-class system, 43200 seconds for the drop-in). Calling clear_cache() or validate( true ) forces an immediate refresh.

Basic license check

The simplest gate is checking whether the license is active at all. This is appropriate for products with a single tier or for functionality that should be available to all licensed users.

// 3-class system
if ( $client->is_active() ) {
    // All licensed functionality
}

// Drop-in
if ( WPLF_Licensing::is_active( 'my-plugin' ) ) {
    // All licensed functionality
}

Gating individual features

When your product has multiple tiers with different feature sets, use has_feature() to conditionally load or display functionality.

// Conditionally register a shortcode
if ( $client->has_feature( 'advanced-forms' ) ) {
    add_shortcode( 'acme_advanced_form', 'acmeforms_render_advanced' );
}

// Conditionally add an admin menu item
add_action( 'admin_menu', function () use ( $client ) {
    if ( $client->has_feature( 'analytics-dashboard' ) ) {
        add_submenu_page(
            'acmeforms-settings',
            'Analytics',
            'Analytics',
            'manage_options',
            'acmeforms-analytics',
            'acmeforms_render_analytics_page'
        );
    }
} );

Feature checks in templates

In front-end templates or output, wrap conditional blocks around feature checks:

<?php if ( $client->has_feature( 'custom-css' ) ) : ?>
    <div class="acme-custom-css-editor">
        <!-- Custom CSS editor UI -->
    </div>
<?php else : ?>
    <p>Upgrade to Pro to unlock the custom CSS editor.</p>
<?php endif; ?>

Tier-based logic

Use get_tier_label() and get_features() when you need to display information about the user's plan or make decisions based on the full tier context.

// Display current plan in the admin
$tier = $client->get_tier_label();
if ( $tier ) {
    printf( '<p>Current plan: <strong>%s</strong></p>', esc_html( $tier ) );
}

// List all available features (useful for debug or settings pages)
$features = $client->get_features();
foreach ( $features as $feature_slug ) {
    printf( '<li>%s</li>', esc_html( $feature_slug ) );
}

Trial detection

Trial licenses behave like active licenses but with an expiration countdown. Use the trial methods to display appropriate messaging or restrict functionality as the trial nears its end.

if ( $client->is_trial() ) {
    $days = $client->get_trial_days_remaining();

    if ( $days <= 3 ) {
        printf(
            '<div class="notice notice-warning"><p>Your trial expires in %d day(s). <a href="%s">Upgrade now</a> to keep using premium features.</p></div>',
            $days,
            esc_url( 'https://example.com/pricing' )
        );
    } else {
        printf(
            '<div class="notice notice-info"><p>Trial active: %d day(s) remaining.</p></div>',
            $days
        );
    }

    // Access the exact end date if needed
    $end_date = $client->get_trial_end(); // e.g. "2026-04-15T00:00:00Z"
}

Combining checks

You can combine is_active(), has_feature(), and is_trial() for more nuanced gating logic:

/**
 * Determine whether a user can access the export feature.
 * Active, non-trial licenses with the 'export' feature enabled.
 */
function acmeforms_can_export() {
    global $acme_client;

    if ( ! $acme_client->is_active() ) {
        return false;
    }

    if ( $acme_client->is_trial() ) {
        return false; // Trials do not get export
    }

    return $acme_client->has_feature( 'export' );
}

Overriding feature checks

The wplf_client_has_feature filter allows you to override the result of has_feature(). This is useful for granting temporary access, beta testing, or multi-plugin bundles.

/**
 * Grant the 'advanced-forms' feature to users who also
 * have the companion plugin active, regardless of tier.
 */
add_filter( 'wplf_client_has_feature', function ( $has_feature, $feature_slug, $client ) {
    if ( 'advanced-forms' === $feature_slug && is_plugin_active( 'acme-addon/acme-addon.php' ) ) {
        return true;
    }
    return $has_feature;
}, 10, 3 );
Parameter Type Description
$has_feature bool The current result from the default feature check.
$feature_slug string The feature slug being checked.
$client WPLF_Client The client instance, allowing access to other license data.

Warning

Use the feature override filter carefully. Granting features outside the server's tier configuration means the server's feature list and the client's behaviour will diverge. This is intentional for edge cases but should not be the primary mechanism for feature assignment.

Practical patterns

Early return pattern

For functions that should only execute with a valid license, check at the top and return early:

function acmeforms_generate_pdf( $form_id ) {
    global $acme_client;

    if ( ! $acme_client->has_feature( 'pdf-export' ) ) {
        return new WP_Error( 'feature_unavailable', 'PDF export requires a Pro licence.' );
    }

    // ... generate PDF
}

Conditional file loading

Avoid loading entire class files for features that are not available:

add_action( 'plugins_loaded', function () {
    global $acme_client;

    if ( $acme_client->has_feature( 'conditional-logic' ) ) {
        require_once __DIR__ . '/includes/conditional-logic.php';
    }

    if ( $acme_client->has_feature( 'payment-fields' ) ) {
        require_once __DIR__ . '/includes/payment-fields.php';
    }
} );

REST API endpoint gating

Gate custom REST API endpoints behind feature checks:

add_action( 'rest_api_init', function () {
    global $acme_client;

    if ( $acme_client->has_feature( 'api-access' ) ) {
        register_rest_route( 'acmeforms/v1', '/submissions', [
            'methods'             => 'GET',
            'callback'            => 'acmeforms_get_submissions',
            'permission_callback' => 'acmeforms_check_api_permission',
        ] );
    }
} );

Next steps