Recipe: Register a tab-bar button

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:

  1. Register the button definition — your content class implements tab_bar_manager_buttons( array $buttons ): array and appends an entry. This makes the button selectable in the admin UI.
  2. Admin enables it — in Mobile App Manager → Navigation → [tab] → Tab Bar Buttons, the admin checks “Show Button” so the setting’s visible flag is 'on'.
  3. 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 empty source will 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_array is per-item, not per-list. Don’t try to batch.

  • 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
Contents

    Need Support?

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