Hook: mam_main_add_tab_bar_item_{slug}

Purpose

Adds a custom action button to the tab bar that appears on a detail screen in the mobile app — for example, the row of icons under a listing, an event, a reservation, or any other “detail” content. Each tab bar item can open a form, open an array of forms, share content, place a phone call, or trigger any other client-side action the app supports.

Use this filter when you want a button that:

  • Is conditional on the current user’s role (owner, manager, staff, admin) or some other state on the post.
  • Pre-populates a Gravity Form with data from the current item.
  • Disappears entirely when it does not apply (returning an empty array hides it).

If the button is purely static (always visible, no per-user logic), the admin UI can configure it without any PHP — you only need this filter when the button needs to be built dynamically per request.


How tab bar items work end-to-end

Building a tab bar item is a three-step process. This filter is step 3.

  1. Register the button definition — your plugin’s content class implements tab_bar_manager_buttons( array $buttons ): array and appends an entry with at least id, title, and type. This makes the button selectable in the MAM admin UI. (See mam-geodirectory/content-classes/local-app-geodirectory-v2-class.php::tab_bar_manager_buttons() for an example.)
  2. Admin enables the button — 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 — when the app loads the list, mam-main walks each enabled tab bar setting and fires mam_main_add_tab_bar_item_{id}. Your callback returns the populated button array, or an empty array to hide it for this user / this item.

Mam-main’s dispatch (in includes/app-settings/local-settings.php) looks roughly like this:

if ( has_filter( 'mam_main_add_tab_bar_item_' . $tab_bar_setting['id'] ) ) {
    $tab_bar_setting = apply_filters(
        'mam_main_add_tab_bar_item_' . $tab_bar_setting['id'],
        $tab_bar_setting,
        $tab_key,
        $data_array['data'][ $itemIndex ]
    );
    if ( is_array( $tab_bar_setting ) && count( $tab_bar_setting ) > 0 ) {
        // ...the button is added to the item's tabbar_item_array.
    }
}

If no filter is registered for the slug, mam-main logs 'missing tabbar' via mamdebug and skips the button.


Signature

apply_filters(
    'mam_main_add_tab_bar_item_{slug}',
    array  $tabbar_button,
    string $tab_key,
    array  $data_array
);
Parameter Type Description
$tabbar_button array The current button definition. Comes in pre-populated with id, title, visible, and the admin-configured display title. You enrich it with icon, action, source, and (optionally) values.
$tab_key string The key of the tab bar setting being processed. Rarely needed — most callbacks ignore it.
$data_array array The detail item being rendered. For GeoDirectory listings this includes id, post_type, post_status, package_id, claimed, address fields, social fields, etc. The exact shape depends on the content class.

Returns: array — the populated $tabbar_button to render, or an empty array [] to hide the button for this item / this user.


Return-value shape

Key Required Description
id yes (preserved from input) Button slug — must match the {slug} in the filter name.
title yes (preserved from input) Display title from admin settings.
icon yes Icon asset name (e.g. 'black_edit', 'white_person', 'black_share', 'black_chat', 'black_approve'). The app resolves these against its bundled icon set.
action yes What the app does when the button is tapped. See “Common action types” below.
source varies Action-specific payload. For form actions, this is the form index (or array of custom-form objects). For share_listing, the post id.
values optional Pre-population values passed to the opened form.
use_off / off_title / off_icon optional Toggle-state overrides if the button has an “off” variant. Most buttons don’t use these.

If a required value can’t be resolved (e.g. the form id isn’t configured yet, or the user isn’t authorized), return [] instead of a half-populated array — this is how every reference implementation handles “should not appear.”


Common action types

These are the values you’ll most often set on $tabbar_button['action']:

Action Source contains Used for
open_form Gravity Forms form index (int as string) Open a single form; pre-fill via values.
open_form_array Array of custom_form definitions Open a list of forms (e.g. one per existing staff member, plus an “Add” entry). See mam_app_settings_tab_bar_button_edit_staff for the exact shape.
share_listing Post id Trigger the native share sheet for the listing.

The action token is interpreted by the mobile client. If you need a new action, coordinate with the app team — adding a string here without client support is a no-op.


Worked example: Edit Listing button (from mam-geodirectory)

Slug: edit_listing → filter name: mam_main_add_tab_bar_item_edit_listing.

Registration in the plugin bootstrap:

$tabbar_buttons = new mam_gd_tab_bar_buttons();
add_filter(
    'mam_main_add_tab_bar_item_edit_listing',
    array( $tabbar_buttons, 'mam_app_settings_tab_bar_button_edit_listing' ),
    10,
    3
);

The callback (simplified — see mam_gd_tab_bar_buttons.php for the full version):

public function mam_app_settings_tab_bar_button_edit_listing( $tabbar_button, $tab_key, $data_array ) {

    $this->user_id = mam_user_id();
    $this->set_user_roles( $data_array['id'] );

    // 1. Authorization gate — bail early if the current user can't edit.
    if ( ! get_option( 'gd-author-can-edit-on-app' )
        || ! ( $this->is_admin || $this->is_manager || $this->is_owner )
    ) {
        return array();
    }

    // 2. Resolve the underlying Gravity Form for this post type.
    $mam_gd_forms = apply_filters( 'mam_generated_forms', array() );
    $form_id      = $mam_gd_forms[ $data_array['post_type'] ] ?? 0;
    $form_index   = apply_filters( 'mam_gf_get_form_from_cache', 0, $form_id );

    if ( $form_index <= 0 ) {
        return array();
    }

    // 3. Build pre-population values from $data_array.
    $form_values       = array();
    $form_values['id'] = $data_array['id'];
    // ...field-by-field pre-population (address, business hours, social, images)...

    // 4. Populate the button and return it.
    $tabbar_button['icon']   = 'black_edit';
    $tabbar_button['action'] = 'open_form';
    $tabbar_button['values'] = $form_values;
    $tabbar_button['source'] = $form_index;

    return $tabbar_button;
}

The four steps — authorize, resolve target, build payload, return — apply to almost every tab bar item callback. If any of the first three fails, return [].


Worked example: a minimal “Edit Reservation” button

This is the shape of the mam-aquaman button added against this filter. The full plugin uses a content class to register the slug; the callback is the smallest possible version:

add_filter( 'mam_main_add_tab_bar_item_edit_reservation',
    [ $frontend, 'edit_reservation_button' ], 10, 3 );

public function edit_reservation_button( $tabbar_button, $tab_key, $data_array ) {

    $form_id = get_option( 'mam_aquaman_edit_reservation' );
    if ( ! $form_id || ! class_exists( 'local_app_gravity_forms' ) ) {
        return array();
    }

    $form_mgr = new local_app_gravity_forms();
    $form     = $form_mgr->get_data_for_app( $form_id );

    // Pre-fill form fields from the current reservation post.
    foreach ( $form['fields'] as $i => $field ) {
        if ( $field['id'] == get_option( 'mam_aquaman_edit_reservation_reservation-date' ) ) {
            $form['fields'][ $i ]['value'] = get_post_meta( $data_array['id'], 'booking_date', true );
        } elseif ( $field['id'] == get_option( 'mam_aquaman_edit_reservation_reservation-time' ) ) {
            $form['fields'][ $i ]['value'] = get_post_meta( $data_array['id'], 'booking_start_time', true );
        } elseif ( $field['id'] == get_option( 'mam_aquaman_edit_reservation_postid' ) ) {
            $form['fields'][ $i ]['value'] = $data_array['id'];
        }
    }

    global $local_app_form_array;
    $local_app_form_array[] = $form;

    $tabbar_button['icon']   = 'black_edit';
    $tabbar_button['action'] = 'open_form';
    $tabbar_button['source'] = (string) count( $local_app_form_array );

    return $tabbar_button;
}

For buttons that aren’t yours but need to be hidden in certain contexts (e.g. hide the generic Share button from staff because they have an Edit button instead), use the sibling filter:

apply_filters( 'mam_main_skip_tab_bar_button', array $tab_bar, array $data_array );

Return array() to hide the button, or return $tab_bar unchanged to keep it. See mam_app_settings_tab_bar_button_skip in mam_gd_tab_bar_buttons.php for the canonical example.

This is preferable to checking conditions inside an “add” filter you don’t own, because the skip filter runs before mam-main’s per-slug dispatch.


Gotchas

  • The slug must round-trip exactly. Whatever id you register in tab_bar_manager_buttons() is the same {slug} that goes in the filter name and the same string that mam-main passes back as $tabbar_button['id']. A typo anywhere silently produces “missing tabbar” debug entries and no button.
  • The filter only fires when the admin has enabled the button. If your callback never runs in QA, first verify the “Show Button” checkbox is on for the relevant tab bar setting. Without visible === 'on', mam-main short-circuits before checking for your filter.
  • Return [] for “don’t show,” not a partially-filled array. Mam-main treats any non-empty array as “render this.” A button with an empty source will still render and then break in the app.
  • Do not call expensive services in the callback. The filter runs once per item per app load — for a list of 50 listings that’s 50 invocations. Cache user role lookups (as mam_gd_tab_bar_buttons does on $this) and avoid HTTP calls or unbatched DB queries.
  • Authorization is your responsibility. Mam-main does not gate tab bar items by role; it just dispatches to your filter. If your button should only appear for owners, the role check must live in the callback. The reference implementation calls set_user_roles( $data_array['id'] ) first and gates on is_owner/is_manager/is_admin/is_staff.
  • The third argument is the per-item data, not the listing query. Each item in the list calls your filter once with that item’s data. Don’t try to batch — pre-compute anything shared (form indexes, package meta) on $this in __construct() or via a memoized helper.

  • Hook: mam_main_skip_tab_bar_button — hide a registered button per item / per user.
  • Hook: mam_app_settings_get_tab_bar_buttons — register the button definition globally (alternative to a content class method).
  • Reference: mam_gd_tab_bar_buttons class — the canonical implementation covering nine distinct tab bar items.

Metadata

Field Value
Article type Hook Reference
Plugin slug mam-main
Applies to plugin version 2.1+
Hook type filter (dynamic — {slug} is replaced with your button id)
Audience PHP developer building a use-case plugin on the MAM Suite platform
Reference implementation mam-geodirectory (includes/mam_gd_tab_bar_buttons.php)
Last verified 2026-04-27
Contents

    Need Support?

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