Hook: mam_iap_require_iap

Signature

apply_filters( 'mam_iap_require_iap', string $has_iap );
Parameter Type Description
$has_iap string The plugin’s resolved value, either 'yes' or 'no'. Reflects the Require IAP after login? general setting (tsl-setting-iap_show_on_login).

Returns: string — 'yes' or 'no'. You must return one of these two values.


Purpose

Provides a last-word override on whether the mobile app’s paywall is active for the current request. The plugin applies this filter twice in sequence on the way out — first to compute inapp_has_iap (does IAP exist at all?) and again to compute inapp_is_required (is purchase required to proceed?). Both keys are sent to the mobile client.

Common reasons to subscribe:

  • Bypass the paywall for a specific user based on the iap_not_required user meta (see Recipe: Bypass IAP for a specific user)
  • Bypass the paywall for a role (editor, subscriber, custom MAM roles)
  • Disable the paywall during a promotional window (e.g., free week)
  • Force-enable the paywall for QA testing on a sandbox build that would otherwise have iap_off_in_sandbox set
  • Honor entitlements from a non-WooCommerce source (B2B license server, comp list, partner integrations)

When it runs

In mam_in_app_purchase_phone_manager::manage_phone_data() (includes/phone-manager.php), after the iap_co_packages array is built but before the admin-exemption short-circuit:

$data_array['inapp_has_iap']     = apply_filters( 'mam_iap_require_iap', $data_array['inapp_has_iap'] );
$data_array['inapp_is_required'] = apply_filters( 'mam_iap_require_iap', $data_array['inapp_has_iap'] );

Notice the filter is applied twice, with the already-filtered value of inapp_has_iap passed back in as the input for inapp_is_required. If you set inapp_has_iap to 'no', the second invocation receives 'no' as input — your callback is responsible for the same logic on both keys, or for distinguishing the two cases on its own.

The plugin does not pass any extra context (like which key is being decided). Both invocations look identical from inside the callback.


Default behavior

There is no default callback. The unsubscribed value is whatever the plugin already computed:

  • inapp_has_iap'yes' if tsl-setting-iap_show_on_login is 'yes', otherwise 'no'
  • inapp_is_required → starts as the post-filter inapp_has_iap, then gets filtered again

Administrators are exempted after this filter, in the next block of manage_phone_data():

if ( $mam_user && in_array( 'administrator', $mam_user->roles ) ) {
    $data_array['inapp_is_required'] = 'no';
    $data_array['inapp_has_iap']     = 'no';
}

This means a mam_iap_require_iap callback that returns 'yes' cannot force the paywall on for an administrator — admins are unconditionally exempt.


Examples

Honor the per-user iap_not_required flag

This is the canonical subscriber: read the user meta written by Recipe: Bypass IAP for a specific user and force 'no' when set.

add_filter( 'mam_iap_require_iap', 'my_app_honor_iap_exemption' );

function my_app_honor_iap_exemption( $has_iap ) {
    do_action( 'mam_update_current_user' );
    global $mam_user_id;
    if ( $mam_user_id > 0 && get_user_meta( $mam_user_id, 'iap_not_required', true ) === 'yes' ) {
        return 'no';
    }
    return $has_iap;
}

Exempt an entire role

add_filter( 'mam_iap_require_iap', 'my_app_exempt_editors' );

function my_app_exempt_editors( $has_iap ) {
    do_action( 'mam_update_current_user' );
    global $mam_user;
    if ( $mam_user && in_array( 'editor', (array) $mam_user->roles, true ) ) {
        return 'no';
    }
    return $has_iap;
}

Time-windowed promo (free week)

add_filter( 'mam_iap_require_iap', 'my_app_free_week' );

function my_app_free_week( $has_iap ) {
    $start = strtotime( '2026-06-01 00:00:00' );
    $end   = strtotime( '2026-06-08 00:00:00' );
    $now   = time();
    if ( $now >= $start && $now < $end ) {
        return 'no';
    }
    return $has_iap;
}

Diverge inapp_has_iap and inapp_is_required

The filter receives no signal about which of the two keys it’s deciding. To make inapp_has_iap = 'yes' (so the app shows the upgrade UI) while inapp_is_required = 'no' (so content remains accessible), the cleanest approach is to gate on the input value itself, since the second invocation receives the post-filter inapp_has_iap:

add_filter( 'mam_iap_require_iap', 'my_app_show_but_dont_require' );

function my_app_show_but_dont_require( $value ) {
    // Both invocations come through here. On the second one, $value already
    // reflects the post-filter inapp_has_iap. We can't reliably distinguish
    // them from inside the callback, so the simplest correct shape is:
    //   - Always allow access (return 'no' for inapp_is_required).
    //   - Leave inapp_has_iap untouched (return 'yes' if input is 'yes').
    // Because the same callback runs both times, the only way to give them
    // different answers is to inspect global state set up between the two
    // apply_filters calls — which there is none of. In practice, you have
    // to choose one strategy for both keys, or split the filter via priority
    // and a request-scoped flag (see Gotchas).
    return $value;
}

This is a known limitation of the hook’s design — see Gotchas.


Gotchas

  • The filter runs twice in a row, with no context distinguishing the two keys. A naive callback that returns 'no' to bypass the paywall will set both inapp_has_iap and inapp_is_required to 'no', which is usually what you want. But if you want to set them differently, you can’t tell them apart from inside the callback. Workarounds: (a) accept that they’ll be the same; (b) use a request-scoped static counter inside the callback to know which invocation is which (fragile); (c) override inapp_is_required in a separate filter further down the pipeline if your stack provides one.
  • Admins are exempted after this filter. Returning 'yes' for an administrator user has no effect on the final payload — the admin-exemption block at the end of manage_phone_data() overrides both keys to 'no'. Don’t rely on this filter to test the paywall as an admin; create a non-admin test user.
  • Always return a string. Returning null, true, false, or omitting the return strips the chain. The mobile keys are typed as string 'yes' / 'no' — anything else can break the app’s deserialization.
  • No early exit. Returning 'no' does not skip later subscribers in the chain. Another callback at higher priority can flip your 'no' back to 'yes'. If your override needs to win, use a high priority (e.g., 999) and accept that it locks out everyone after you.
  • mam_update_current_user is your responsibility. The plugin does call it earlier in manage_phone_data(), so $mam_user and $mam_user_id are populated globally by the time this filter runs. But if you’re consulting them from a callback that might run in another context (a CLI script, a unit test), trigger the action yourself first.
  • This hook does not affect iap_co_packages. Returning 'no' hides the paywall but does not remove the product list from the payload. The list is still sent and a deployed app may still cache it. To suppress the products themselves, you’d need to filter the payload elsewhere.

Verification

This article was last verified against:

  • Plugin: mam-inapp-purchase-manager v2.0
  • Source: includes/phone-manager.php (manage_phone_data — the two apply_filters calls and the admin-exemption block)

Re-verify whenever the filter is applied a different number of times, the admin-exemption block is moved or removed, the inapp_has_iap / inapp_is_required keys are renamed, or context (a second filter argument identifying the key) is added.


  • Plugin overview: mam-inapp-purchase-manager
  • Recipe: Configure IAP settings — sets the upstream tsl-setting-iap_show_on_login value
  • Recipe: Bypass IAP for a specific user — admin-side equivalent that depends on this filter
  • Hook: mam_iap_active_subscriber_product
  • Hook: mam_iap_purchase

Metadata

Field Value
Article type Hook Reference
Plugin slug mam-inapp-purchase-manager
Applies to plugin version 2.0+
Category Extending MAM Suite
Hook type filter
Audience PHP developer
Last verified 2026-05-01
Contents

    Need Support?

    Can’t find the answer you’re looking for? Don’t worry we’re here to help!