Goal
Build a sibling plugin that hooks into mam-main’s pipelines without modifying mam-main itself.
The pattern every sibling follows
Every well-behaved MAM Suite sibling plugin (mam-geodirectory, mam-chat-manager, mam-special-offers, etc.) follows the same shape:
- Plugin file with header — standard WP plugin metadata
- Activation gate — check that mam-main is at the required version, and that the customer is entitled
- Bootstrap on
plugins_loaded— only after the entitlement check passes - Register hooks for the surfaces this plugin extends
- Don’t import mam-main classes — extend through filters and actions only
Bootstrap skeleton
<?php
/**
* Plugin Name: MAM My Plugin
* Description: Adds X to the MAM Suite mobile app.
* Version: 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) exit;
add_action( 'plugins_loaded', function () {
// 1. Check mam-main is present and at required version.
$required_version = '1.9.1';
if ( ! apply_filters( 'mam_check_required_version', false, 'mam-main', $required_version ) ) {
return;
}
// 2. Check entitlement.
$entitlement = apply_filters( 'mam_plugin_entitlement', false, 'mam-my-plugin' );
if ( ! $entitlement || ! empty( $entitlement['blocked'] ) ) {
return;
}
// 3. Bootstrap.
new MAM_My_Plugin_Manager();
}, 11 ); // priority 11 to ensure mam-main has loaded its register_*() chain
Common extension surfaces
| Surface | Hook | Recipe |
|---|---|---|
| Inject data into the mobile JSON | mam_get_phone_data_before_send |
Hook: mam_get_phone_data_before_send |
| Add a custom notification type | mam_notification_list |
Recipe: Register a notification type |
| Send a notification | mam_notification_send_message |
Hook: mam_notification_send_message |
| Add a tab-bar button | mam_main_add_tab_bar_item_{slug} + a content-class registration |
Hook: mam_main_add_tab_baritem{slug} |
| Hide a tab-bar button per item | mam_main_skip_tab_bar_button |
Hook: mam_main_skip_tab_bar_button |
| Register a custom field type | mam_form_manager_process_field_type_{type} |
Recipe: Register a custom field type |
| Register a custom (non-GF) form | mam_form_manager_get_forms_from_plugins |
Hook: mam_form_manager_get_forms_from_plugins |
| Register a scheduled cron task | mam_cron_manager |
Recipe: Register a scheduled cron task |
| Add a content class (button type) | $local_app_content registration + class | Recipe: Register a content class |
| Inject into the publish payload | mam_fastlane_settings |
Hook: mam_fastlane_settings |
| Inject extra profile fields | mam_add_fields_to_user_profile |
Hook: mam_add_fields_to_user_profile |
| Append entries to the settings menu | mam_main_content_class_settings |
Hook: mam_main_content_class_settings |
| React to favorites toggle | mam_manage_favorites |
Hook: mam_manage_favorites |
What you DON’T do
- ❌ Import or
require_oncemam-main class files - ❌ Read or write mam-main’s owned options directly (use the filters)
- ❌ Touch mam-main’s owned tables directly (use repositories where possible)
- ❌ Rename frozen contracts (you’d break the apps in the field)
- ❌ Fire mam-main’s hooks from outside mam-main (with a few documented exceptions like
mam_notification_send_message) - ❌ Construct option keys yourself for per-role / per-button settings — use the cascade filters
Conventions
- Plugin slug prefixed with
mam-(e.g.,mam-my-plugin) - Notification slugs prefixed with the plugin slug (
mam-my-plugin-event_name) - Cron ids prefixed (
mam_my_plugin_nightly_sync) - Custom hook names prefixed (
mam_my_plugin_*) - Option keys prefixed (
mam_my_plugin_*) — and use the migration framework for any rename - Class names prefixed (
MAM_My_Plugin_*ormam_my_plugin_*) - *Stay out of the `local-app-
,tsl_, andmam-` core namespaces**
Performance discipline
- Hot path:
mam_app_settings_get_settingruns 100+ times per phone-data build. Don’t add slow subscribers without object cache. - Hot path:
mam_get_phone_data_before_sendfires once per request, but contributing 50ms per subscriber across 70 subscribers = 3.5s of latency. Be cheap. - Cache aggressively —
wp_cache_get/wp_cache_set, transients, static per-request caches. - Never make HTTP calls in the phone-data path without aggressive caching.
- Use the cursor mechanism — when your data hasn’t changed, return
nullfor your section and let the app keep its cache.
Versioning
- mam-main uses semver-ish versions (
2.1.11) - Bump your plugin’s version when you ship a behavior change; the WPMAM update mechanism (
mam-main-software-manager-api) pulls updates - Document the minimum mam-main version your plugin requires
Testing your plugin
- The Previewer app is your primary test surface
tests/snapshot/snapshot-phone-data.php(mam-main’s regression harness) captures JSON fixtures — useful for verifying you don’t accidentally change a section you don’t own- For form submissions, the Outbox Viewer under Notifications shows what was dispatched
- For push, watch APNs / FCM dashboards plus
MAM_PushResult->errors
Related articles
- Plugin: mam-main
- Architecture overview
- Frozen public contracts reference
- Recipe: Register a notification type
- Recipe: Register a custom field type
- Recipe: Register a content class
- Recipe: Register a scheduled cron task
- Recipe: Register a tab-bar button
- Hook: mam_get_phone_data_before_send
- Hook: mam_notification_send_message
- Hook: mam_app_settings_get_setting
- Hook: mam_plugin_entitlement
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 | varies (1–8 hours per plugin) |
| Last verified | 2026-05-02 |
