Send custom data to the app from a content class (get_data_for_app)

Metadata

Field Value
Article type Recipe (Developer)
Plugin slug mam-main
Applies to plugin version all current mam-main releases (26.x)
Category Extending MAM Suite
Audience PHP developer
Estimated time 1–2 hours
Last verified 2026-06-03

Goal

Send custom data to the mobile app from a content class by implementing get_data_for_app(), the method the button-rendering loop calls to build the payload for each button. By the end you will know which of the method’s two call shapes applies to your button, what to return, and how the phone-data pipeline and mam_tab_manager hooks let you enrich that payload further before it ships to the device.

This recipe assumes you already have a content class registered. If you do not, start with Recipe: Register a content class and come back here for the payload-shaping step.


Background

A content class is the server-side handler for one type of mobile UI element (Login, Map, Social Media, WP Post Content, and so on). Every button a customer places on a screen is backed by one of these classes. When the app asks the server for its data, the phone-data pipeline walks the customer’s button arrays, and for each button it instantiates the matching content class and calls get_data_for_app(). Whatever that method returns is the data the app actually receives for that button.

There is no formal interface declaration — the content-class contract is duck-typed. A class that omits get_data_for_app() will still appear in the admin builder, but its buttons render nothing on the device.


Where get_data_for_app() is called

The call happens inside the button-rendering loop that runs during the pipeline’s content phase (MAM_Phone_Data_Pipeline::phase_content()), in includes/app-connect/main-json-manager.php. The relevant lines:

$content_obj = new $content_class_name();

if ( 'html' === $this_button['vc_type'] ) {
    $this_button = $content_obj->get_data_for_app( $value['source'], $this_button );
} else {
    $this_button['data'] = $content_obj->get_data_for_app( $value['source'], false, $title );
    if ( 'list' === $this_button['vc_type'] ) {
        $this_button['use_subcategories'] = 'yes';
    }
}

The single most important thing to understand is that the call shape depends on your content class’s vc_type. Pick the shape that matches the vc_type you registered in $local_app_content.

Shape A — vc_type is html: return the whole button array

When vc_type === 'html', the loop passes the in-progress button array as the second argument and expects your method to return the modified button array. You receive the button, set keys on it, and hand it back.

local_app_wp_post_content (local-app-wp-post-content-class.php, registered with vc_type => 'html') is the canonical example:

public function get_data_for_app( $page_id, $this_post ) {

    $post    = get_post( $page_id );
    $content = iconv( 'UTF-8', 'ASCII//TRANSLIT', $post->post_content );

    $this_post['data']           = $content;
    $this_post['post_image']     = ' ';
    $this_post['post_imageurl']  = ' ';
    $this_post['post_extension'] = ' ';
    $this_post['post_image_id']  = ' ';

    if ( has_post_thumbnail( $post->ID ) ) {
        // ... resolve the featured image and set the post_image* keys ...
    }

    return $this_post; // the whole button array, enriched
}

Note that this shape sets data itself, alongside sibling keys (post_image, post_imageurl, etc.). The first argument ($page_id / $value['source']) is the per-button content source the customer selected in the builder.

Shape B — non-html vc_type: return just the data value

For every other vc_type (social_media, list, offDirectory, and so on), the loop assigns your return value to $this_button['data'] and supplies false and the button title as the second and third arguments. Your method returns only the value that belongs under data — a scalar, a string, or an array, depending on what your button needs.

local_app_social_media (local-app-social-media-class.php, vc_type => 'social_media') shows the minimal form:

public function get_data_for_app( $category ) {

    $data = '';

    if ( get_option( 'local-app-social-facebook' ) && 800 === $category ) {
        $data = get_option( 'local-app-social-facebook' );
    }
    // ... one branch per social network ...

    if ( strlen( $data ) > 0 ) {
        $data = $category; // hand back the resolved source id
    }

    return $data;
}

Because PHP does not enforce argument counts on this duck-typed contract, the social-media class declares only $category even though the loop passes three arguments — the extra two are simply ignored. Declare the parameters you actually use.

If your method returns an empty value, the loop substitutes a safe default: ' ' for normal buttons, or a { nodata: 1 } sentinel for list / offDirectory types. Returning a falsy value is therefore handled, not fatal — but it means an empty button on the device.


Steps

1. Confirm your vc_type and pick the matching shape

Look at how you registered the class:

$local_app_content['My Type'] = array(
    'content_type' => 'My Button',
    'class'        => 'mam_my_plugin_button',
    'vc_type'      => 'my_button', // <- this decides the call shape
);

If vc_type is html, use Shape A (receive and return the button array). Otherwise use Shape B (return the data value only). If you are unsure, html is the right choice when you need to set several sibling keys on the button (images, extensions, flags); the non-html shape is right when the app only needs a single data payload.

2. Implement get_data_for_app()

Read the per-button content source from the first argument (the value the customer chose in the builder), resolve whatever site-level or per-button settings you need, and return the payload in the shape from step 1.

public function get_data_for_app( $source, $second = false, $title = '' ) {

    // $source is the per-button content source from the builder.
    $payload = array(
        'type'   => 'my_button',                 // a token the mobile client recognizes
        'target' => $this->resolve_target( $source ),
        'label'  => $this->get_setting( 'label', '' ),
    );

    // Shape A (html vc_type): merge into the button array and return it.
    // if ( is_array( $second ) ) {
    //     $second['data'] = $payload;
    //     return $second;
    // }

    // Shape B: return just the data value.
    return $payload;
}

Keep the method fast. It runs once per button per phone-data build, so cache any expensive lookups rather than querying inside the loop.

3. Make sure the mobile client recognizes your payload

Anything you put in the payload — especially a type token — is inert unless the mobile client knows how to render it. Server-side data alone does not make a button appear; coordinate the payload shape and any new tokens with the client team. An unrecognized type renders nothing.

4. (Optional) Enrich the button through mam_tab_manager

After get_data_for_app() runs, the same loop fires the mam_tab_manager filter on the assembled button:

$this_button = apply_filters( 'mam_tab_manager', $this_button );

This is the per-button enrichment hub (~43 subscribers across the suite). Use it from a sibling plugin to add or override per-button keys — icons, action targets, prefill values — without owning the content class itself. Dispatch on $button['tab_type'] (the key you registered in $local_app_content), optionally gated on $button['tab_location'], so you only touch the buttons you mean to:

add_filter( 'mam_tab_manager', function ( array $button ): array {

    if ( ( $button['tab_type'] ?? '' ) !== 'My Type' ) {
        return $button;
    }

    $button['icon']   = $button['icon'] ?: 'black_default';
    $button['action'] = 'open_target';

    return $button;
}, 10 );

Shared keys are last-writer-wins, so coordinate with other plugins if you stomp on icon, action, or similar. Never change $button['id'] — the caller expects it back unchanged. See Hook: mam_tab_manager.

5. (Optional) Inject supporting data via mam_get_phone_data_before_send

If your button needs data that does not live on the button itself — a shared lookup table, a list of records the detail screen will key into, a top-level block the app reads once — add it to the response through the legacy mam_get_phone_data_before_send filter rather than cramming it into get_data_for_app(). This filter fires once per build during the content phase and is the main extension point for sibling plugins (~70 subscribers). Follow the priority conventions: priority 10 for the default cohort, and stay below 1000 so you run before the home_cats injection. See Hook: mam_get_phone_data_before_send and Phone data pipeline overview.

6. Test in the Previewer

  • Add a button of your type in Mobile App Mgr → App Navigation and configure its content source.
  • Place the button on a screen.
  • Open the Previewer and confirm the button renders and behaves as expected.
  • If nothing renders, check the two most common causes: an empty return from get_data_for_app(), or a type token the client does not recognize.

Where this sits in the pipeline

The phone-data pipeline builds the app’s JSON response in four phases (auth → settings → content → finalize). Your get_data_for_app() runs in the content phase, inside the button-rendering loop, followed immediately by mam_tab_manager and then mam_final_button_settings for that same button. The finalize phase injects home_cats last. Knowing this ordering tells you where to do each kind of work: per-button payload in get_data_for_app(), per-button overrides in mam_tab_manager, response-wide data in mam_get_phone_data_before_send.


Gotchas

  • The call shape is decided by vc_type, not by your method signature. Reading the wrong shape is the most common mistake: an html class that returns only a data value will drop every sibling key the app expects.
  • No interface enforcement. A missing or misnamed get_data_for_app() fails silently — the button shows up in the admin and renders nothing on the device.
  • Hot path. The method runs once per button per build. A screen with many buttons multiplies the cost; cache lookups instead of querying inside the method.
  • Empty returns are defaulted, not fatal. The loop substitutes ' ' (or a nodata sentinel for list types) when you return a falsy value — which looks like an empty button rather than an error.
  • Client coordination is mandatory. New payload keys or type tokens do nothing until the mobile client supports them.
  • get_data_for_app is also a method name elsewhere. The forms manager (mam-gravity-forms-content-class.php) defines an unrelated get_data_for_app() with a different signature for building form payloads. Do not confuse the two contracts; this recipe is about the content-class button contract.

Verification

This article was last verified against:

  • mam-main/includes/app-connect/main-json-manager.php (the button-rendering loop and call sites)
  • mam-main/includes/content-classes/local-app-wp-post-content-class.php (Shape A — html vc_type)
  • mam-main/includes/content-classes/local-app-social-media-class.php (Shape B — non-html vc_type)
  • mam-main/includes/content-classes/README.md (duck-typed interface)
  • mam-main/user_docs/06a-phone-data-pipeline-overview.md
  • mam-main/user_docs/09j-hook-mam-tab-manager.md

Re-verify whenever the call sites in main-json-manager.php change, the duck-typed content-class interface gains a formal declaration, or the vc_type-to-call-shape mapping changes.


  • Recipe: Register a content class
  • Content classes overview
  • Phone data pipeline overview
  • Hook: mam_tab_manager
  • Hook: mam_get_phone_data_before_send
  • Hook: mam_final_button_settings
Was this article helpful?
Contents

    Need Support?

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