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.
- Register the button definition — your plugin’s content class implements
tab_bar_manager_buttons( array $buttons ): arrayand appends an entry with at leastid,title, andtype. This makes the button selectable in the MAM admin UI. (Seemam-geodirectory/content-classes/local-app-geodirectory-v2-class.php::tab_bar_manager_buttons()for an example.) - Admin enables the button — 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 — 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;
}
Related: hiding a registered button (mam_main_skip_tab_bar_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
idyou register intab_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 emptysourcewill 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_buttonsdoes 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 onis_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
$thisin__construct()or via a memoized helper.
Related articles
- 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_buttonsclass — 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 |
