Goal
Run your own server-side logic the moment an app user taps the star on a row — favoriting or unfavoriting a post. By the end you will have a handler hooked to the mam_manage_favorites action that fires inside the app’s AJAX request, reads the post id and the toggle direction, and does whatever your plugin needs (persist to your own usermeta, sync to a CRM, recompute a count, send a push, and so on).
This is the same mechanism mam-geodirectory uses to keep its gd_user_favourite_post list in sync. You are adding a sibling handler alongside it.
Prerequisites
- MAM Suite installed with mam-main activated (this is where the favorites AJAX dispatcher lives).
- A favorites-capable surface in the app — the Favorites content class rendering, with the report favorites flag turned on so the app actually sends toggle events to the server. Without that flag the app never fires the AJAX, so the action never runs.
- PHP 8.1+ and the ability to load code on the WordPress side (your own plugin, or a small companion plugin). Do not put this in a theme
functions.phpon a production app build. - Familiarity with WordPress actions (
add_action) and usermeta.
Why the flag matters: the toggle event is opt-in per content class. The Favorites content class exposes a report favorites setting (mirrored into the phone-data JSON as
report_favorites). If it is off, the star is display-only and nothing reaches the server.
How the toggle reaches your code
Before writing the handler, it helps to know exactly what fires. When the user taps the star, the app calls mam-main’s AJAX endpoint (wp_ajax_mam_main_ajax) with:
| Request field | Meaning |
|---|---|
subaction |
mam_manage_favorites — names the action to fire |
id |
the post id being toggled |
pingaction |
favorite or unfavorite — the direction of the toggle |
pid |
the app’s request token (validated before anything runs) |
The dispatcher validates the pid, confirms the subaction starts with mam_ and has a registered action, then calls:
do_action( $_REQUEST['subaction'] ); // i.e. do_action( 'mam_manage_favorites' )
That is the key detail: the action fires with no arguments. Your handler does not receive $post_id / $user_id / $is_favorite parameters. Instead it reads the request itself — $_REQUEST['id'] for the post, $_REQUEST['pingaction'] for the direction — and gets the current app user from mam_user_id().
After all handlers run, the dispatcher returns fresh phone-data to the app, so your persisted change is reflected on the next screen refresh.
Steps
1. Hook the action
Register your handler against mam_manage_favorites. Because the action fires with no arguments, you do not need a custom priority/arg-count for the args themselves — the default registration is enough.
add_action( 'mam_manage_favorites', 'myplugin_on_favorite_toggle' );
2. Read the toggle from the request
Inside the handler, pull the post id and direction from the request and the user from mam_user_id(). Sanitize everything — this is an app-facing endpoint.
function myplugin_on_favorite_toggle(): void {
$post_id = absint( $_REQUEST['id'] ?? 0 );
$pingaction = sanitize_key( $_REQUEST['pingaction'] ?? '' );
$user_id = mam_user_id();
if ( ! $post_id || ! $user_id ) {
return; // Nothing to do without a real post and a signed-in user.
}
$is_favorite = ( 'favorite' === $pingaction );
// ... your side-effect goes here (Step 3).
}
pingaction is the string favorite or unfavorite; treat anything that is not favorite as an unfavorite, the same way the core handlers do.
3. Scope to your own content, then run your side-effect
The action fires for every favorite toggle in the app, regardless of which plugin owns the post type. Guard early so you only act on your own content, then do your work.
// Only handle our post type.
if ( get_post_type( $post_id ) !== 'my_plugin_listing' ) {
return;
}
$favs = (array) get_user_meta( $user_id, 'my_plugin_favorites', true );
if ( $is_favorite ) {
$favs[] = $post_id;
$favs = array_unique( $favs );
} else {
$favs = array_diff( $favs, array( $post_id ) );
}
update_user_meta( $user_id, 'my_plugin_favorites', array_values( $favs ) );
Prefix your usermeta key with your plugin slug (here, my_plugin_favorites) so it never collides with another sibling’s list. mam-geodirectory stores its list under gd_user_favourite_post; mam-main’s own generic handler uses favorites. There is no shared favorites table — each plugin owns its own.
4. Keep the work fast
The action runs synchronously inside the AJAX request that the app is blocking on. Heavy work (external API calls, large recomputes) delays the response and the user’s next screen. If you need to do something slow — sync to a CRM, send a push — enqueue it rather than running it inline:
if ( $is_favorite ) {
wp_schedule_single_event( time(), 'myplugin_sync_favorite_to_crm', array( $post_id, $user_id ) );
}
5. Verify
- Confirm the Favorites surface has report favorites enabled (otherwise the app never sends the event).
- In the app, favorite one of your posts, then unfavorite it.
- Check your usermeta (or your side-effect target) updated in both directions. A quick way:
get_user_meta( $user_id, 'my_plugin_favorites', true );
If nothing changes, the most common cause is the report-favorites flag being off, or your get_post_type() guard not matching your actual post type.
Gotchas
- No arguments are passed. Read
idandpingactionfrom$_REQUESTand the user frommam_user_id(). Do not assume ado_action( ..., $post_id, $user_id, $is_favorite )signature. - Your handler runs for all favorite toggles. Always guard with a
get_post_type()(or equivalent) check so you don’t act on another plugin’s content. - No de-duplication across plugins. If two handlers both claim the same post id, both persist. Coordinate via post-type checks.
- One toggle = one fire. There is no batching and no pending/undo state; the change is immediate.
- App-facing input. Sanitize
idandpingaction; never trust them raw. - The favorites list in phone-data is an array of strings. If your plugin also contributes ids back into the rendered favorites list (a separate concern from this action), return them as strings — older mobile parsers reject numeric ids.
Related
- Hook: mam_manage_favorites (mam-main hook reference)
- Content class: Favorites (the renderer and the report-favorites flag)
- Hook: mam_gd_manage_favorites (mam-geodirectory’s own post-toggle action, fired after its handler runs)
What’s next
If you also need your favorited items to appear in the app’s Favorites list, that is the rendering side, owned by the Favorites content class and assembled from sibling plugins. See Content class: Favorites for how a sibling contributes its ids into the list returned in phone-data.
