Goal
Register a new content class — a button type that customer admins can add to screens via the no-code builder — without modifying mam-main’s bundled class set.
Background
A content class represents one type of mobile UI element (Login, Map, Web URL, etc.). The 21 bundled classes live in mam-main/includes/content-classes/. New classes can also be registered from a sibling plugin, as long as they:
- Append to the global
$local_app_content[]array - Implement the duck-typed interface (see Content classes overview)
- Use a unique
classidentifier (frozen once customer sites have button instances)
Steps
1. Decide on the class name
Convention: mam_<plugin>_<kind>_button. Once customer sites have created button instances of this class, the name is frozen — renaming orphans every saved instance in local-app-button-array*.
Example: mam_my_plugin_qr_scanner_button.
2. Write the class
class mam_my_plugin_qr_scanner_button {
public function __construct() {
// Optional: wire AJAX endpoints, hook listeners, etc.
}
public function app_settings_categories() {
return array(
array( 'title' => 'QR Scanner', 'slug' => 'qr_scanner' ),
);
}
public function app_settings() {
return array(
array(
'category' => 'qr_scanner',
'title' => 'Allowed QR formats',
'variable' => 'allowed_formats',
'type' => 'select',
'id' => 'mam_my_plugin_allowed_formats',
'environment' => 'global',
'options' => array( 'qr', 'aztec', 'datamatrix' ),
),
// ... more fields
);
}
public function get_blank_content_html() {
return '<p>Add a QR Scanner button.</p>';
}
public function get_content_type_form( $id, $content_source ) {
return $this->build_form_html( $id, $content_source );
}
public function create_initial_buttons( $this_array ) {
// Optionally seed an instance for new sites:
$this_array[] = array(
'id' => 'qr_default',
'title' => 'Scan QR',
'content_type' => 'QR Scanner',
'class' => 'mam_my_plugin_qr_scanner_button',
'icon' => 'black_qr',
'visible' => 'on',
);
return $this_array;
}
public function get_data_for_app( $category ) {
return array(
'type' => 'qr_scanner',
'allowed_formats' => $this->get_setting( 'allowed_formats', 'qr' ),
'on_scan_action' => 'open_form',
'on_scan_target' => $this->get_form_index_for_scan_result(),
);
}
private function get_setting( $key, $default ) {
return apply_filters(
'mam_app_settings_get_setting',
$default,
mam_current_request()->user_role(),
'mam_my_plugin',
$key
);
}
}
3. Register in $local_app_content
add_action( 'plugins_loaded', function () {
global $local_app_content;
$local_app_content['QR Scanner'] = array(
'content_type' => 'QR Scanner',
'class' => 'mam_my_plugin_qr_scanner_button',
'vc_type' => 'qr_scanner',
);
}, 12 ); // priority 12 to ensure mam-main has loaded first
⚠️ The class value is frozen once customer sites have instances. Pick carefully.
4. Coordinate with the mobile client
The type value in your get_data_for_app() return (qr_scanner in the example) must be recognized by the mobile client. Adding a string here without client support is a no-op — the client renders nothing.
5. Test in the Previewer
- Add a button of your new type in Mobile App Manager → Buttons
- Configure the per-button settings
- Place the button on a screen
- Open the Previewer; verify the new button renders and behaves correctly
When sibling plugins go further
For complex content classes (lists with detail screens, like mam-geodirectory’s listing class), you’ll typically also:
- Register a content-section element (in
content-class-elements) - Wire up tab-bar buttons for the detail screen (
mam_main_add_tab_bar_item_{slug}) - Add per-row enrichment via
mam_tab_managerfor tab-bar items - Hook
mam_get_phone_data_before_sendto inject the data the class needs
See mam-geodirectory/content-classes/local-app-geodirectory-v2-class.php for a comprehensive worked example.
Frozen surface callouts
- Class name in
$local_app_content[$key]['class']is frozen once customer sites have button instances. Renaming orphans every saved button. content_typekey is the user-facing label in the admin. Changing it changes what admins see.vc_typeis a Visual Composer integration leftover and mostly inert today, but downstream code still reads it — keep it set.- *`local-app-button-array`** option is the storage; it’s a frozen public contract.
Gotchas
- No interface enforcement. A class that omits
get_data_for_appwill appear in the admin UI but render nothing on the device. - Class name freezing. Rename only with a
MAM_Migration_Tasks-style plan to rewrite every customer’s serialized button arrays. $local_app_contentis a global, populated at file-include time. Any code that reads it must run after mam-main has loaded its content-class-manager.- Mobile client coordination required. Server-side registration alone doesn’t make your button render — the client needs to know the
typetoken. - Adding via a sibling plugin vs. a fork of mam-main: prefer the sibling-plugin path. mam-main’s bundled class set is reserved for cross-customer functionality.
Related articles
- Content classes overview
- Recipe: Add a button
- Hook: mam_app_settings_get_setting
- Hook: mam_get_phone_data_before_send
- Hook: mam_main_add_tab_baritem{slug}
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–4 hours |
| Last verified | 2026-05-02 |
