Add fields to the app user profile and persist them on registration

Goal

Add one or more custom fields to a MAM app’s user profile form, make those fields persist when the user saves their profile (or registers), and surface the saved values back to the app through the phone-data payload.

This is the standard three-part pattern for extending the app’s identity surface from a sibling plugin or a site-specific snippet, without touching mam-main core files:

  1. Inject the field into the profile form via mam_add_fields_to_user_profile.
  2. Persist the submitted value via the per-field save filter mam_user_roles_save_addl_user_profile_field_{meta_key}.
  3. Read it back to the app via a mam_get_phone_data_before_send subscriber.

Prerequisites

  • MAM Suite with mam-main active. The hooks below are defined in mam-main’s user-roles form manager.
  • A place to put PHP that loads on the website (a sibling plugin, a must-use plugin, or a site-specific helper plugin). Do not edit mam-main core files.
  • A usermeta key you own. Prefix it with your plugin slug to avoid collisions with WordPress core meta and with other plugins.
  • Familiarity with WordPress update_user_meta() / get_user_meta().

Steps

1. Inject the field into the profile form

Subscribe to mam_add_fields_to_user_profile. The filter is applied by the user-roles form manager when it assembles the profile form’s field list. Each entry you add is merged into the form alongside the built-in fields (first name, last name, email, and so on).

In the live source the form manager reads two keys off each entry — a display title and a meta_key — and uses the meta_key both as the form field’s identifier and as the usermeta key it writes to:

add_filter( 'mam_add_fields_to_user_profile', function ( array $fields ): array {

    $fields[] = array(
        'title'    => 'Loyalty card ID',          // shown as the field label
        'meta_key' => 'my_plugin_loyalty_id',     // form key AND usermeta key
        // Only `title` and `meta_key` are read by mam-main's consumers of this
        // filter. Any other keys you add (type, required, placeholder, etc.)
        // are ignored by the profile-form builder in the current source.
    );

    return $fields;
} );

Things to know about this filter, from the source:

  • It is applied with the in-progress field array as its only argumentapply_filters( 'mam_add_fields_to_user_profile', array() ). There is no $user_id, so don’t register with a two-argument form expecting one; derive the current user from get_current_user_id() instead, and never assume an existing user during signup.
  • The meta_key is the field’s identity. It is what the next step’s dynamic save filter name is built from, so pick it deliberately and keep it stable.

2. Persist the submitted value

When the profile form is saved, the form manager walks the submitted fields. For any field whose key has a registered save filter, it fires:

apply_filters( 'mam_user_roles_save_addl_user_profile_field_' . $meta_key, $value );

The dynamic suffix is your field’s meta_key from step 1. Two behaviors are load-bearing and easy to get wrong:

  • You must persist the value yourself. The form manager only calls update_user_meta() automatically for fields that have no save filter registered. The moment you register a save filter for a key, mam-main hands the whole save off to you — it does not write the meta and does not use your return value. If your callback doesn’t call update_user_meta(), the value is silently dropped.
  • The filter receives the raw $value only. The source fires it as apply_filters( 'mam_user_roles_save_addl_user_profile_field_' . $meta_key, $value ) — no $user_id second argument. Get the user id from the current request with get_current_user_id().
add_filter( 'mam_user_roles_save_addl_user_profile_field_my_plugin_loyalty_id', function ( $value ) {

    $user_id = get_current_user_id();
    if ( ! $user_id ) {
        return $value;
    }

    // Normalize / validate.
    $clean = preg_replace( '/[^A-Z0-9]/i', '', (string) $value );

    // YOU must persist — returning a value is not enough.
    update_user_meta( $user_id, 'my_plugin_loyalty_id', $clean );

    return $clean;
} );

Decide explicitly what an empty submission means: an empty $value will still reach your callback, so choose whether empty deletes the meta or preserves the prior value.

This per-field filter has no structured error channel — returning a value can’t surface a validation message to the user. For cross-field validation or to veto a save with a message, use the whole-payload pre-save filter (mam_user_roles_modify_user_profile_values_before_save) instead. See the related hook references.

3. Run anything that must happen once, at registration

If your field needs a one-time action when the account is first created (seed a default, sync to an external system, queue a welcome flow), hook mam_user_roles_after_create_user. It fires only for users created through mam-main’s registration flow — not for users added in wp-admin. In the source it is fired as apply_filters( 'mam_user_roles_after_create_user', array(), $user_id ): the first argument is an unused pass-through array (the filtered value), and the second argument is the new user id. No submitted registration data is passed — derive whatever you need from $user_id and get_user_meta():

add_filter( 'mam_user_roles_after_create_user', function ( $result, int $user_id ) {

    // Seed a default if the field wasn't part of signup.
    if ( '' === (string) get_user_meta( $user_id, 'my_plugin_loyalty_id', true ) ) {
        update_user_meta( $user_id, 'my_plugin_loyalty_id', '' );
    }

    return $result;
}, 10, 2 );

Return the first argument ($result) unchanged — it’s the filtered value, and returning it keeps you a well-behaved filter even though every call site currently ignores the return. Don’t re-send the bundled welcome notification from here; the registration flow already dispatches it.

4. Read the value back into the app

The field now persists, but the app won’t see it until you put it in the phone-data payload. Subscribe to mam_get_phone_data_before_send — the primary mobile-API extension point — and inject your value:

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

    $user_id = get_current_user_id();
    if ( $user_id ) {
        $data_array['my_plugin_loyalty_id'] =
            get_user_meta( $user_id, 'my_plugin_loyalty_id', true );
    }

    return $data_array;
}, 10 );

Notes for this subscriber:

  • This filter is on the hot path — it runs on every phone-data request. Keep the callback cheap; a get_user_meta() read is fine, an HTTP call is not.
  • Use priority 10 (the default content-provider cohort). Don’t register at priority 1000, which is reserved for mam-main’s home_cats injection.
  • The key you add (my_plugin_loyalty_id here) appears at the top level of the JSON response. Once a deployed app reads it, that key is effectively a frozen contract — renaming it breaks shipped apps.
  • Whether your value lands at the top level or nested under the existing user block is your choice; match whatever the app side expects to read.

Verify

  1. Open the app’s profile screen (or the in-app profile-completion form). Your field should render with the title you supplied.
  2. Enter a value and save. Confirm it persisted: check wp_usermeta for your meta_key, or get_user_meta( $user_id, 'my_plugin_loyalty_id', true ).
  3. Trigger a fresh phone-data fetch from the app (or call the phone-data endpoint) and confirm your key is present in the JSON with the saved value.
  4. Register a brand-new user through the app’s signup flow and confirm any mam_user_roles_after_create_user logic ran exactly once.

Common pitfalls

  • Field renders but never saves. You registered the field (step 1) but not the save filter (step 2) — or your save filter is registered under a name that doesn’t exactly match mam_user_roles_save_addl_user_profile_field_{meta_key}. A mismatched suffix silently never fires.
  • Save filter fires but nothing persists. You returned a value but didn’t call update_user_meta(). Registering the save filter opts you out of mam-main’s automatic write.
  • Value saves but the app never shows it. You skipped step 4. Persistence and the phone-data payload are separate concerns.
  • Assuming $user_id from the filter. Neither the inject filter nor the per-field save filter passes a $user_id. Use get_current_user_id().
  • Reading existing meta during signup. At signup the user may not exist yet; guard reads accordingly.

  • Hook: mam_add_fields_to_user_profile — inject step, full field schema.
  • Hook: mam_user_roles_save_addl_user_profile_field_{key} — per-field save, persistence rules.
  • Hook: mam_user_roles_after_create_user — post-registration lifecycle.
  • Hook: mam_user_roles_modify_user_profile_values_before_save — whole-payload validation and veto (use this for user-facing validation errors).
  • Hook: mam_get_phone_data_before_send — the mobile-API payload extension point, priority conventions, and the frozen-contract rules.

What’s next

  • Add per-field validation with a user-facing error by moving validation into mam_user_roles_modify_user_profile_values_before_save.
  • If your field should be cache-aware, follow the cursor pattern in the phone-data subscriber reference so the field only re-sends when its underlying value changes.
Was this article helpful?
Contents

    Need Support?

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