Recipe: Register a content class

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 class identifier (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_manager for tab-bar items
  • Hook mam_get_phone_data_before_send to 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_type key is the user-facing label in the admin. Changing it changes what admins see.
  • vc_type is 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_app will 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_content is 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 type token.
  • 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.

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

    Need Support?

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