Goal
Change what the geofilter location picker shows — and how it behaves — without touching the mam-geofilters core files. By the end you will know which of the four filters to reach for and how to wire each one up:
- Add or remove cities in the built-in list →
mam_wc_geofilters_locations - Replace the whole list with data from another source →
mam_geofilter_list - Force the picker on or off for a role or feature flag →
mam_force_geofilters - Override the search radius per role or per request →
mam_geofilter_radius
This is the code-level companion to the admin-side Recipe: Manage the city list and Recipe: Enable geofilters. Use the recipes when configuration is enough; use the filters here when it isn’t.
Prerequisites
mam-geofiltersv2.1.1+ active, with the plugin entitlement granted (it self-exits in its constructor otherwise).mam-mainactive.- A place to register filters — a small site-specific plugin or your theme’s
functions.php. Do not edit files insidemam-geofilters. - Familiarity with WordPress
add_filter(). - For the “replace the list” path: a data source (a custom post type, an external API, or a taxonomy such as GeoDirectory).
One concept to hold onto before you start: there are two list paths, and only one runs per request.
- Default-list path — the plugin builds its hardcoded 56-city list, applies the admin hide list, then runs
mam_wc_geofilters_locations. - Delegation path — if any callback is attached to
mam_geofilter_list, the plugin skips the default list andmam_wc_geofilters_locationsentirely, and uses whatever your callback returns.
The switch is a plain has_filter( 'mam_geofilter_list' ) check in includes/phone-manager.php. Pick the path that matches your goal; mixing them is the most common source of “my cities disappeared” bugs.
Steps
1. Decide: extend, or replace?
| You want to… | Use | Path |
|---|---|---|
| Add a few markets to the built-in list | mam_wc_geofilters_locations |
Default-list |
| Trim the built-in list to a regional subset | mam_wc_geofilters_locations |
Default-list |
| Source cities from a CPT / API / taxonomy | mam_geofilter_list |
Delegation |
If you only need to add or remove a handful of cities, stay on the default-list path (Step 2). If your cities come from somewhere other than the hardcoded list, take over completely (Step 3).
2. Add or remove cities (mam_wc_geofilters_locations)
This filter receives the location array directly. Each entry has text, lat, and lon. You must return the array.
Add a city:
add_filter( 'mam_wc_geofilters_locations', 'my_app_add_rochester' );
function my_app_add_rochester( $locations ) {
$locations[] = [
'text' => 'Rochester, NY',
'lat' => '43.1566',
'lon' => '-77.6088',
];
return $locations;
}
Trim to a regional subset:
add_filter( 'mam_wc_geofilters_locations', 'my_app_west_coast_only' );
function my_app_west_coast_only( $locations ) {
$allowed = [ 'San Jose', 'Oakland', 'Los Angeles', 'San Diego', 'San Francisco' ];
return array_values( array_filter(
$locations,
fn( $loc ) => in_array( $loc['text'], $allowed, true )
) );
}
The filter runs after the admin hide list is applied and before the alphabetical sort, the zero-coordinate drop, and the “Nearby” prepend. You don’t need to sort or prepend anything yourself.
Watch for:
- Forgetting
return $locations;strips the entire list. latorlonof0is silently dropped late in the builder. If your source defaults missing coordinates to0, those entries vanish with no warning.- Duplicate
textvalues confuse the admin show/hide UI, which keys ontext. - This runs on every phone-data request. If you pull from a database or API, cache the result.
3. Replace the entire list (mam_geofilter_list)
This filter receives the whole mobile JSON payload ($data_array), not just the list. Your callback writes the array into $data_array['geoFiltersLocations'] and returns $data_array.
add_filter( 'mam_geofilter_list', 'my_app_geofilter_list' );
function my_app_geofilter_list( $data_array ) {
$data_array['geoFiltersLocations'] = [
[ 'text' => 'Brooklyn', 'lat' => '40.6782', 'lon' => '-73.9442', 'active' => 'on' ],
[ 'text' => 'Manhattan', 'lat' => '40.7831', 'lon' => '-73.9712', 'active' => 'on' ],
[ 'text' => 'Queens', 'lat' => '40.7282', 'lon' => '-73.7949', 'active' => 'on' ],
];
return $data_array;
}
After your callback returns, the pipeline still drops entries whose active is set and not equal to 'on' (entries with no active key are kept), sorts alphabetically, and prepends a “Current Location” / GeoIP entry when appropriate. Let it do that work.
Watch for:
- Any callback at any priority flips the switch. Even
add_filter( 'mam_geofilter_list', '__return_array' )makeshas_filter()true and disables the default list. If you need to fall back to the default list conditionally, do it inside one callback — neverremove_filter()from elsewhere. mam_wc_geofilters_locationsis bypassed on this path. Any additions you registered there will silently disappear once you delegate. This is exactly what happens whenmam-geodirectoryis active — it owns this filter and sources cities from GeoDirectory taxonomies.- Always return
$data_array. Forgetting the return strips the whole payload, not just the list.
4. (Optional) Force geofilters on or off (mam_force_geofilters)
The on/off decision is resolved from the per-role settings tsl-use_geofilters and tsl-geofilters_show_as_icon, then passed through this filter as a bool for a last-word override. Return a bool.
add_filter( 'mam_force_geofilters', 'my_app_geofilters_for_premium' );
function my_app_geofilters_for_premium( $use_geofilters ) {
if ( current_user_can( 'premium_member' ) ) {
return true;
}
return $use_geofilters; // leave every other case untouched
}
Despite the name, this is a plain filter — returning false overrides a true upstream, so it can disable as well as enable. Useful for things the role-settings UI can’t express: a query parameter, a cookie, custom user meta, or a kill switch. If you toggle on $_REQUEST, sanitize it — this runs on every phone-data request.
5. (Optional) Override the search radius (mam_geofilter_radius)
The plugin’s own callback resolves the radius (in miles) in this order: $_REQUEST['radius'] → per-role setting tsl-setting-geofilter_radius → default 25 → capped at 3000. Register your callback at a later priority to override the resolved value. Return an int.
add_filter( 'mam_geofilter_radius', 'my_app_premium_radius', 20 );
function my_app_premium_radius( $radius ) {
if ( current_user_can( 'premium_member' ) ) {
return min( 100, max( $radius, 50 ) );
}
return $radius;
}
Watch for:
- The 3000-mile cap only fires inside the core callback. A later callback can return any positive integer; re-apply
min( 3000, $radius )yourself if you widen it and still want the cap. $_REQUEST['radius']short-circuits the per-role setting. If a user’s radius doesn’t match their role config, check whether the client is sending an explicitradiusparameter.- This filter does not turn the picker on or off — a radius of
0doesn’t disable it. Usemam_force_geofiltersfor that.
6. Verify in the app
Trigger a phone-data request (refresh the app or hit the phone-data endpoint) and confirm:
- Your added/removed cities appear (or your replacement list does).
- The picker is present or absent as your
mam_force_geofilterslogic intends. - Distance filtering matches the radius you set.
Because the list builder consumes the admin hide option before mam_wc_geofilters_locations, cities you add in code only become visible to the admin show/hide UI after a request has populated the list at least once.
Related
- Hook: mam_wc_geofilters_locations — full reference for adding/removing cities
- Hook: mam_geofilter_list — full reference for replacing the list
- Hook: mam_force_geofilters — full reference for the on/off override
- Hook: mam_geofilter_radius — full reference for the radius override
- Recipe: Manage the city list — admin-side hide/edit, no code
- Recipe: Enable geofilters — admin-side on/off and radius
- Plugin overview: mam-geofilters
What’s next
If your replacement list comes from a real data source, wrap the lookup in a transient — the relevant filter fires on every phone-data request, and an uncached database or API call there will slow every app load. Once your code-level list is stable, hand off to the admin recipes so non-developers can hide individual cities without redeploying.
