Mobile JSON shape

Summary

The JSON payload returned by the local_app_get_phone_data AJAX action is the contract between the WordPress site and the mobile app. Mobile clients in the field consume specific keys at specific paths; the shape is frozen at the top level.

This article catalogs the top-level structure, the per-section conventions, and what sibling plugins must obey when injecting.


Top-level shape

{
  "user": { "id": "0", "role": "anonymous", ... },
  "settings": { "theme": {...}, "branding": {...}, ... },

  "main_button_array": [ { ... }, { ... }, ... ],
  "left_button_array": [ { ... }, { ... }, ... ],
  "tab_bar_settings": { "buttons": [...], "visible": true },

  "form_data": { "<form_id>": { ... }, ... },
  "nonce": "<wp-nonce>",
  "ajax_url": "https://example.com/wp-admin/admin-ajax.php",

  "home_cats": { ... },
  "notifications": [ { ... }, ... ],
  "unread_count": 3,

  "favorites": [ "123", "456" ],
  "report_favorites": "yes",

  "geofilter_default_name": "City",
  "track_views": "yes",

  "<sibling_plugin_keys>": ...
}

Keys not listed above are sibling-plugin contributions injected at mam_get_phone_data_before_send.


Conventions

yes / no strings, not booleans

Many flags ship as strings ("yes" / "no" or "on" / "off") rather than booleans. Frozen — older mobile parsers expect strings. Don’t ship true/false in places where the convention is "yes".

Strings for numeric ids

Post ids, user ids, button ids, and form indexes are typed as strings in the payload, not integers. Older iOS / Android JSON parsers reject numeric ids in places where strings were established.

null becomes ""

mam_replace_null_with_empty_string() runs in phase_finalize and replaces every null in field values with "". Older parsers raised on null in string fields. Don’t try to undo this transformation.

Section root null means “no change”

The cursor cache mechanism uses null at the section root to signal “no change since cursor”. That’s a different semantic from null inside fields. mam_replace_null_with_empty_string() only touches field values, not section roots.


Frozen top-level keys

The following keys are observed and parsed by deployed mobile clients. Renaming them breaks apps in the field:

Key Role Frozen by
user.id, user.role Auth state mam-main
main_button_array, left_button_array Nav button arrays mam-main
tab_bar_settings Tab bar config mam-main
form_data Form definitions keyed by form id forms-manager
nonce WordPress nonce for subsequent AJAX mam-main
ajax_url The admin-ajax.php URL mam-main
home_cats Home-screen category structure mam-main (MAM_Main_Manager::manage_phone_data)
notifications, unread_count In-app notification list notifications-manager
favorites, report_favorites, hide_remove_all_favorites Favorites sync mam-main + sibling plugins
geofilter_default_name Default city mam-geofilters
track_views Analytics opt-in flag various

Sibling-plugin injection

Sibling plugins inject their own top-level keys via mam_get_phone_data_before_send. The convention:

  • Use a plugin-specific prefix (my_plugin_listings, not listings)
  • Don’t overwrite existing keys
  • Don’t introduce keys that conflict with frozen names
  • Use string types for ids and "yes"/"no" for flags

Examples from existing plugins (mam-geodirectory):

Key Shape Plugin
track_views 'yes' mam-geodirectory
report_favorites 'yes' mam-geodirectory
hide_remove_all_favorites 'yes' mam-geodirectory
data_has_search_index 'yes' mam-geodirectory
use_category_in_search_results 'yes' mam-geodirectory
do_not_sort_picker 'yes' mam-geodirectory
geofilter_default_name string mam-geodirectory
has_initial_form_to_display 'yes' mam-geodirectory
initial_form_to_display int (1-based form index) mam-geodirectory

Common section shapes

Button (nav button)

{
  "id": "btn_42",
  "title": "Home",
  "icon": "black_home",
  "action": "open_screen",
  "source": "screen_id_or_form_index",
  "values": { ... },
  "visible": "on",
  "style_*": { ... }
}

Form definition

{
  "id": "42",
  "title": "Contact us",
  "fields": [
    { "id": "1", "type": "text", "label": "Your name", "required": true },
    ...
  ],
  "settings": { "colors": {...}, "submit_label": "Send" }
}

Notification

{
  "id": "1",
  "title": "Your order shipped",
  "body": "Tracking: ABC123",
  "channel": "push",
  "status": "sent",
  "ts": "2026-05-02T...",
  "data": { ... }
}

Map marker

{
  "id": "post_123",
  "title": "Venue name",
  "lat": "40.7128",
  "lon": "-74.0060",
  "marker_icon": "https://...",
  "snippet": "..."
}

Gotchas

  • null is dangerous. Older parsers fail on null in string fields. The pipeline strips fielded nulls automatically; if you bypass output shaping, you’ll regret it.
  • Frozen top-level keys. Don’t try to “clean up” key naming — apps in the field reference the existing names.
  • Strings for ids, not ints. Even when an id is numeric, ship it as "123" not 123.
  • yes/no strings, not bools. Same reason.
  • Don’t overwrite sibling-plugin keys. Two plugins fighting over listings produces last-writer-wins; coordinate via a shared key prefix.
  • Cursor null vs field null are different. Section-root null means “no change since cursor”; field null becomes "".

  • Phone data pipeline overview
  • Phone data pipeline phases
  • Cursor cache mechanism
  • Hook: mam_get_phone_data_before_send
  • Frozen public contracts reference

Metadata

Field Value
Article type JSON Key Reference
Plugin slug mam-main
Applies to plugin version 2.1.11+
Category App Settings Reference
Audience PHP developer
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!