Settings cascade overview

Summary

Every settable knob in mam-main flows through mam_app_settings_get_setting (read) and mam_app_settings_set_setting (write). 100+ call sites use the read filter. Sibling plugins register handlers per-key, so reads are extensible without touching the app-settings subsystem.

The cascade is per-role + per-button + global. The fallback chain is implemented inside mam_app_settings_data_manager, not by chained filter subscribers — subscribers shouldn’t try to re-implement the fallback themselves.


Read signature

$value = apply_filters(
    'mam_app_settings_get_setting',
    $default,        // value to return if no override exists
    $role,           // role slug (e.g., 'subscriber', 'admin', 'cloning')
    $category,       // settings category (e.g., 'general-settings', 'login', 'map')
    $key             // the setting key (e.g., 'tsl-setting-disable_caching')
);

Returns the resolved value, falling back through the cascade.


The cascade

Look up setting in this order:

1. Per-button override        (if invoked from a button context)
2. Per-role override          ($role-specific value)
3. Global setting             (site-wide value)
4. Default                    (the $default arg)

The data manager walks these layers; the first hit wins. A subscriber that reads from a custom store can register against the filter at priority 10 to inject between (1) and (2), but most use cases are covered by the built-in cascade.


Write signature

apply_filters(
    'mam_app_settings_set_setting',
    $value,          // new value
    $role,           // role scope (or empty for global)
    $category,       // settings category
    $key             // setting key
);

Returns the persisted value. Writes always target a specific scope — there’s no “write everywhere” semantics.


Common categories

Category Subsystem Example keys
general-settings mam-main core tsl-setting-disable_caching, tsl-setting-geofilter_radius
login user-roles require_login, magic_link_enabled
branding app-settings theme colors, logo
push push-notification-manager per-channel toggles
forms forms-manager per-form colors, default field display
<plugin> sibling-plugin contributions per-plugin namespaced keys

Sibling plugins are free to add their own categories.


Per-button vs per-role vs global

  • Per-button — the setting is stored inside the button’s blob (local-app-button-array*). Reading uses $button_id context.
  • Per-role — stored in a role-specific option (e.g., tsl-setting-geofilter_radius_subscriber).
  • Global — stored in a site-wide option (e.g., tsl-setting-geofilter_radius).

The cascade looks per-button first, then per-role, then global, then default.


Frozen public contract

mam_app_settings_get_setting and mam_app_settings_set_setting are frozen public contracts. ~100 call sites across the suite + sibling plugins expect the existing signature and cascade behavior. Frozen contract — do not rename.


Hot path warning

mam_app_settings_get_setting is hot — it runs hundreds of times per phone-data build. Adding heavy work to a subscriber for this filter degrades app-load latency for every customer:

  • ❌ Don’t make HTTP calls from a subscriber
  • ❌ Don’t run expensive DB queries
  • ❌ Don’t allocate large objects per call
  • ✅ Do use object cache (wp_cache_get / wp_cache_set) for any non-trivial lookup
  • ✅ Do return the input $default immediately if your subscriber doesn’t apply

A poorly-written subscriber can add 200+ ms to every phone-data request — multiply by every customer’s app launches.


Read recipes

Read with a fallback default:

$radius = apply_filters(
    'mam_app_settings_get_setting',
    25,                              // fallback
    mam_current_request()->user_role(),
    'general-settings',
    'tsl-setting-geofilter_radius'
);

Override a setting from a sibling plugin:

add_filter( 'mam_app_settings_get_setting', function ( $value, $role, $category, $key ) {
    if ( $key === 'my_plugin_special_value' && $role === 'subscriber' ) {
        return 'override';
    }
    return $value;
}, 10, 4 );

Write recipes

Write a per-role value:

apply_filters(
    'mam_app_settings_set_setting',
    'new value',
    'subscriber',
    'general-settings',
    'tsl-setting-geofilter_radius'
);

Filter Use
mam_app_settings_get_buttons Per-role button definitions
mam_app_settings_get_layout_settings Layout config
mam_app_settings_get_home_screen_stack Home-screen stack
mam_app_settings_get_content_section_settings Per-button content sections
mam_app_settings_get_tab_bar_settings Tab-bar config
mam_app_settings_get_tab_bar_buttons Tab-bar button list
mam_app_settings_get_default_sections Default content sections
mam_app_settings_get_form_colors Form theme colors

These are scoped equivalents — same filter pattern, different return shapes.


Gotchas

  • Hot path. Be careful adding subscribers; cache aggressively.
  • The cascade is in the data manager. Don’t try to re-implement fallback by chaining subscribers — you’ll fight the built-in logic.
  • Frozen contract. Don’t rename. ~100 call sites across the suite.
  • set_setting is per-scope. A site-wide write requires $role = '' (or whatever your site’s “global” sentinel is); a per-role write needs an explicit role.

  • Frozen public contracts reference
  • Per-button and per-role settings
  • Button array storage
  • Codex and settings discovery
  • Hook: mam_app_settings_get_setting
  • Hook: mam_app_settings_set_setting

Metadata

Field Value
Article type Plugin Overview
Plugin slug mam-main
Applies to plugin version 2.1.11+
Category App Settings Reference
Audience PHP developer
Last verified 2026-05-02
Contents

    Need Support?

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