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_idcontext. - 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
$defaultimmediately 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'
);
Related read filters
| 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_settingis per-scope. A site-wide write requires$role = ''(or whatever your site’s “global” sentinel is); a per-role write needs an explicit role.
Related articles
- 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 |
