Goal
Add a custom action button to the detail-screen tab bar — for example, an “Edit Reservation” button on a reservation detail page that opens a Gravity Forms form pre-populated from the current reservation.
Background
Tab-bar buttons follow a three-step pattern:
- Register the button definition — your content class implements
tab_bar_manager_buttons( array $buttons ): arrayand appends an entry. This makes the button selectable in the admin UI. - Admin enables it — in Mobile App Manager → Navigation → [tab] → Tab Bar Buttons, the admin checks “Show Button” so the setting’s
visibleflag is'on'. - Build the button payload at request time — register a
mam_main_add_tab_bar_item_{id}filter to populate the button per-item, per-user.
Steps
1. Choose a slug
The slug is the button id. Convention: snake_case, prefixed with your plugin or content type — edit_reservation, share_listing, edit_listing. The same slug appears in three places: the registered definition, the filter name, and the returned button’s id field. All three must match exactly.
2. Register the button definition
class MAM_My_Plugin_Tab_Bar_Buttons {
public function register( $buttons ) {
$buttons[] = array(
'id' => 'edit_reservation',
'title' => 'Edit Reservation',
'type' => 'frontend',
);
return $buttons;
}
}
add_action( 'plugins_loaded', function () {
$registry = new MAM_My_Plugin_Tab_Bar_Buttons();
add_filter( 'mam_app_settings_get_tab_bar_buttons',
array( $registry, 'register' ),
10
);
}, 12 );
After this, the button appears as a selectable entry in the admin UI.
3. Admin enables the button
- Mobile App Manager → Navigation → Tab Bar (or the per-content-type tab-bar admin)
- Pick Edit Reservation for one of the slots
- Check Show Button
4. Implement the runtime filter
add_filter( 'mam_main_add_tab_bar_item_edit_reservation',
array( $this, 'edit_reservation_button' ),
10,
3
);
public function edit_reservation_button( $tabbar_button, $tab_key, $data_array ) {
// 1. Authorization: only show for the reservation's owner.
$owner_id = (int) get_post_field( 'post_author', $data_array['id'] );
if ( $owner_id !== mam_current_request()->user_id() ) {
return array(); // hide for non-owners
}
// 2. Resolve the form to open.
$form_id = get_option( 'mam_my_plugin_edit_reservation_form_id' );
$form_index = apply_filters( 'mam_gf_get_form_from_cache', 0, $form_id );
if ( $form_index <= 0 ) {
return array(); // no form configured — hide
}
// 3. Build pre-population values.
$values = array(
'reservation_id' => $data_array['id'],
'reservation_date' => get_post_meta( $data_array['id'], 'booking_date', true ),
'reservation_time' => get_post_meta( $data_array['id'], 'booking_start_time', true ),
);
// 4. Populate the button.
$tabbar_button['icon'] = 'black_edit';
$tabbar_button['action'] = 'open_form';
$tabbar_button['source'] = $form_index;
$tabbar_button['values'] = $values;
return $tabbar_button;
}
5. (Optional) Hide a button you don’t own per item
If you need to hide a button defined elsewhere (e.g., hide the generic Share button from staff because you have an Edit button instead):
add_filter( 'mam_main_skip_tab_bar_button',
function ( $tab_bar, $data_array ) {
if ( ( $tab_bar['id'] ?? '' ) === 'share_listing'
&& $this->is_staff_for( $data_array['id'] ) ) {
return array();
}
return $tab_bar;
}, 10, 2 );
This is preferable to checking conditions inside an “add” filter you don’t own — mam_main_skip_tab_bar_button runs before the per-slug dispatch.
6. Verify
- The button appears in the admin’s tab-bar configuration UI
- With Show Button checked, the runtime filter fires (verify with a debug log)
- For users who shouldn’t see it, the button is hidden (filter returns
[]) - For users who should, the button renders with the configured icon and opens the form pre-populated
Common action types
| Action | Source contains | Used for |
|---|---|---|
open_form |
Form index (int as string) | Open a single GF form, pre-fill via values |
open_form_array |
Array of form definitions | Open a list of forms (e.g., one per existing staff member, plus an “Add” entry) |
share_listing |
Post id | Trigger native share sheet for the listing |
phone_call |
Phone number | Tap-to-call |
web_url |
URL | Open in webview / browser |
The action token is interpreted by the mobile client. Adding a new action requires coordination with the app team.
Gotchas
- Slug round-trip. The id you register, the filter name
{slug}segment, and$tabbar_button['id']returned by your filter must all match exactly. A typo silently produces “missing tabbar” debug entries and no button. - The filter only fires when
visible === 'on'. Without the admin toggle, your filter never runs. - Return
[]for “don’t show”, not a partially-filled array. Mam-main treats any non-empty array as “render this.” A button with emptysourcewill render and break in the app. - Hot path. The filter runs once per item per app load — for a list of 50 items, 50 invocations. Cache user role lookups, avoid HTTP calls.
- Authorization is your responsibility. Mam-main doesn’t gate by role; it just dispatches.
$data_arrayis per-item, not per-list. Don’t try to batch.
Related articles
- Recipe: Configure the tab bar
- Recipe: Add a button
- Hook: mam_main_add_tab_baritem{slug}
- Hook: mam_main_skip_tab_bar_button
- Hook: mam_tab_manager
- Hook: mam_final_button_settings
- Content classes overview
Metadata
| Field | Value |
|---|---|
| Article type | Recipe (Developer) |
| Plugin slug | mam-main |
| Applies to plugin version | 2.1.11+ |
| Category | Extending MAM Suite |
| Audience | PHP developer |
| Estimated time | 1–2 hours |
| Last verified | 2026-05-02 |
