Recipe: Inject custom data into the mobile JSON

Goal

Add a top-level key (or augment existing nested data) to the JSON payload returned by local_app_get_phone_data, so the mobile app can render your sibling plugin’s content.


Prerequisites

  • A sibling plugin with its own slug
  • A clear understanding of what data you want to inject
  • Coordination with the mobile client team (the app must know how to render your key)

Steps

1. Pick the right priority

If your subscriber… Use priority
Reads no other data, only injects 10 (default cohort)
Adds to home_cats 10 (must run before 1000)
Reads listings injected by another plugin 100
Validates form state 199
Performs a use-case total override 9999

Most subscribers use 10. See Priority conventions for phone-data subscribers.

2. Choose a top-level key

Convention: prefix with your plugin slug to avoid collisions. my_plugin_widget, my_plugin_listings, my_plugin_alerts. Don’t use bare names like widget or listings — those collide.

3. Register the subscriber

add_filter( 'mam_get_phone_data_before_send',
    function ( array $data_array ): array {

        $user_id = mam_current_request()->user_id();

        $data_array['my_plugin_widget'] = array(
            'enabled'  => true,
            'message'  => 'Hello from my plugin',
            'items'    => $this->get_items_for_user( $user_id ),
        );

        return $data_array;
    },
    10
);

4. Use string-typed ids and yes/no flags

Mobile parsers in the field expect strings for ids and "yes"/"no" for flags:

$data_array['my_plugin_widget'] = array(
    'enabled'   => 'yes',                    // not true
    'item_id'   => (string) $item_id,        // not (int)
    'count'     => count( $items ),          // ints fine for non-id numbers
);

5. Wire up cursor caching

If your data is expensive to build, integrate with the cursor mechanism so the app can skip rebuilding when nothing has changed:

add_filter( 'mam_get_phone_data_before_send',
    function ( array $data_array ): array {

        $cursor      = JSON_Cursor_Manager::get_cursor( get_current_user_id() );
        $section_ts  = (int) get_option( 'my_plugin_data_changed_at', 0 );

        if ( $section_ts <= $cursor ) {
            $data_array['my_plugin_widget'] = null;     // signal "no change"
            return $data_array;
        }

        $data_array['my_plugin_widget'] = $this->build_widget();
        return $data_array;
    },
    10
);

When you mutate underlying data, bump the timestamp:

update_option( 'my_plugin_data_changed_at', time() );

For changes that affect every user (a setting change), call JSON_Cursor_Manager::reset_cursor().

6. Test in the Previewer

# Snapshot the JSON before adding your plugin:
curl 'https://example.com/wp-admin/admin-ajax.php?action=local_app_get_phone_data' > before.json

# Activate your plugin, then snapshot again:
curl 'https://example.com/wp-admin/admin-ajax.php?action=local_app_get_phone_data' > after.json

# Diff:
diff before.json after.json

Verify your key appears at the right level with the right shape. The tests/snapshot/snapshot-phone-data.php regression harness can be used for systematic verification.

7. Coordinate with the mobile team

Server-side injection is half the work. The mobile client must know how to consume your key. Without client support, the data is shipped but ignored.


Patterns to follow

Per-user data

$user_id = mam_current_request()->user_id();
$data_array['my_plugin_user_data'] = $user_id ? $this->build_user_data($user_id) : null;

Authentication-aware

if ( ! mam_current_request()->user() ) {
    return $data_array;     // anonymous — skip
}

Cloning admins

Cloning admins are previewing as another role. bypass_caching is on; your section will rebuild every request.

if ( mam_current_request()->is_cloning() ) {
    // Always rebuild — don't trust the cache
}

Anti-patterns to avoid

  • ❌ Make HTTP calls without caching — a 500ms call adds 500ms per app launch
  • ❌ Run unbatched DB queries (one query per row × 70 subscribers × every user)
  • ❌ Overwrite keys you don’t own — coordinate via prefixed keys
  • ❌ Return null from the filter (return $data_array)
  • ❌ Use bool / int types where the convention is string
  • ❌ Defeat the cursor by always rebuilding
  • ❌ Register at priority 1000 (reserved for home_cats)

Frozen JSON conventions

  • Top-level key naming — prefix with your plugin slug
  • Strings for ids — even when numeric ("123" not 123)
  • "yes" / "no" for flags — not true / false
  • null becomes "" in field values via mam_replace_null_with_empty_string() — section-root null is preserved (cursor signal)

See Mobile JSON shape for the full conventions.


  • Phone data pipeline overview
  • Phone data pipeline phases
  • Mobile JSON shape
  • Cursor cache mechanism
  • Priority conventions for phone-data subscribers
  • Hook: mam_get_phone_data_before_send
  • Hook: mam_local_app_data
  • Frozen public contracts reference

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 30–60 minutes
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!