Add a custom form field type that round-trips through the app

Metadata

Field Value
Article type How-to (Developer)
Plugin slug mam-main (forms-manager subsystem)
Category Extending MAM Suite
Audience PHP developer
Estimated time 30–60 minutes
Last verified 2026-06-03

Goal

Register a custom Gravity Forms field type — a signature pad, a barcode scan, a “pick from my threads” picker — so that the value the mobile app sends on submit is transformed server-side and persisted with the rest of the form entry. This is the round trip: the app submits a value for the field, MAM Suite recognizes the field type, runs your transform, and stores it.


Prerequisites

  • mam-main active, with the forms-manager subsystem in use (Gravity Forms-backed app forms).
  • A Gravity Forms form already wired into the app via Mobile App Manager → Forms (the form that will carry your custom field).
  • PHP developer access to a plugin or functions.php-equivalent where you can register filters. Do this in a sibling MAM plugin or a site mu-plugin, not by editing mam-main.
  • Coordination with the mobile client team. The app is what actually renders a signature pad or barcode scanner. Server-side registration handles the data; it does not draw the widget. A genuinely new field type needs a coordinated mobile release.

How the round trip actually works

When the app submits a form, the forms-manager walks each submitted field in map_values_to_fields() (form-data-manager.php). For each field it reads the field’s type:

$type = strtolower( $form_field['field_type'] );

Known types (name, address, fileupload, select, date, time, etc.) are handled by dedicated handle_* methods. Anything it does not recognize falls to the default case, which is where your custom type plugs in:

default:
    if ( has_filter( 'mam_form_manager_process_field_type_' . $type ) ) {
        $field['value'] = apply_filters(
            'mam_form_manager_process_field_type_' . $type,
            $field['value'],        // current value being built
            $form_field['value'],   // raw submitted value from the app
            $field                  // the field array (slug, type, etc.)
        );
    } else {
        $field['value'] = $form_field['value']; // passthrough, untouched
    }
    break;

Two things to take from this:

  1. {type} is the field’s field_type string, lowercased. That is the dynamic segment in the filter name.
  2. If no filter is registered for that type, the raw submitted value is stored verbatim. That is the “silently accepts anything” failure mode — the app can submit garbage and it lands in the entry meta unchanged.

After values are mapped, the submit path (submit-app-form.php) creates/updates the post and writes one meta row per field slug:

foreach ( $values as $value ) {
    update_post_meta( $post_id, $value['slug'], $value['value'] );
}

$this_response = apply_filters(
    'mam_for_gravity_forms_form_submitted_' . $form_id,
    $form_id,
    $post_id,
    $values
);

So the round trip uses two registration points:

Hook Fires Purpose
mam_form_manager_process_field_type_{type} Per field, during value mapping Transform/validate the raw submitted value before it is stored as the field’s value
mam_for_gravity_forms_form_submitted_{form_id} Once, after the entry post is created Post-process the whole submission — re-host an attachment, push to a CRM, build the response

Register the transform and your value is shaped correctly. Add the per-form handler when you need to do something with the persisted entry as a whole.


Steps

1. Pick the field-type slug

Choose a snake_case string: signature_pad, barcode_scan, thread_picker. This must equal the field_type the field reports on submit (lowercased), and it must match the type the mobile client uses to pick its renderer. Same string on both sides.

Which field reports a non-standard type to the app is driven per-field by the Special handling control under Mobile App Manager → Forms → [your form]. Selecting an option there saves a numeric handling code to the option mam-for-gf-form-field-special-handling-{form_id}-fieldid-{field_id} (via the mam_gf_save_special_handling AJAX action in forms-manager-main.php), which is emitted into the form JSON as the field’s special_handling. The app maps that code to a renderer and to the field_type string it sends back on submit — so the exact Special handling option that corresponds to your custom renderer is a client-build detail to confirm with the mobile team.

2. Register the submit-time value transform

add_filter(
    'mam_form_manager_process_field_type_signature_pad',
    function ( $value, $submitted_value, $field ) {

        // $submitted_value is what the app sent. For a signature pad,
        // that's typically a base64 PNG data URI.
        if ( ! is_string( $submitted_value )
            || ! preg_match( '#^data:image/png;base64,#', $submitted_value ) ) {
            // Decide your failure policy. Returning empty here stores nothing;
            // structured per-field error reporting is handled at the form level.
            return '';
        }

        // Transform if needed (e.g. strip the data-URI prefix, or hand off
        // to a media-attachment routine and store the resulting URL/ID).
        return $submitted_value;
    },
    10,
    3 // three args: $value, $submitted_value, $field
);

Return the value you want stored. Whatever you return becomes $field['value'] and is written to post meta under the field’s slug. This filter is your only chance to sanitize before persistence — there is no default validation for unknown types.

3. (Optional) Register the per-form submission handler

Use this when storing the raw value is not enough — you want to re-host a signature as a real media attachment, fan the entry out to an external system, or customize the response the app receives.

add_filter(
    'mam_for_gravity_forms_form_submitted_42', // 42 = your form id
    function ( $form_id, $post_id, $values ) {

        foreach ( $values as $value ) {
            if ( ( $value['type'] ?? '' ) !== 'signature_pad' ) {
                continue;
            }
            // $value['value'] is the transformed value from step 2,
            // already saved to meta on $post_id. Re-host, notify, etc.
        }

        // Return the response payload the app should receive.
        // See the contact-form / standard-handler response shape for
        // the status/type/message contract.
        return /* $this_response */;
    },
    10,
    3 // $form_id, $post_id, $values
);

The return value here flows into $this_response and ultimately the app’s submission response. Return an array in the same shape standard-form-handler.php produces: status, type, message, process_normal, and send_notifications. submit-app-form.php reads process_normal and send_notifications off that array to decide whether to run the normal Gravity Forms entry flow and fire notifications, so include them.

4. Verify nothing silently passes through

The single most common mistake is registering only the mobile renderer and forgetting the server transform. When that happens the default branch runs the passthrough and stores whatever the app sent — there is no error. So check explicitly:

  • Submit a valid value → the stored meta for the field’s slug holds the transformed value.
  • Submit an invalid value → your transform’s failure policy is honored (empty, rejected, or flagged), not stored verbatim.
  • Confirm has_filter( 'mam_form_manager_process_field_type_signature_pad' ) returns true in the running site — a typo in the slug means your filter never fires and you are back to passthrough.

5. Coordinate the mobile renderer

Hand the field-type slug to the mobile team. The app must recognize that type and draw the right control (signature canvas, barcode camera, thread list). Without client support the field falls back to whatever default the app uses for unknown types, and the round trip looks broken even though the server side is correct.


Notes on the cache

The forms-manager has a mam_form_manager_cache_manager class, but in the current code it manages an in-memory array ($local_app_form_array) of form definitions for the duration of a request — it exposes is_form_in_array() and add_or_update_form_in_array(), not an invalidate() method, and there is no mam-form-cache-{form_id} transient.

If your custom field type only affects submit-time value handling (steps 2–3), there is nothing to invalidate — the filter runs on every submission. If you later add build-time output that depends on a stored setting and you observe stale form JSON in the app, treat cache invalidation as an open question and check how that specific form’s definition is regenerated rather than calling a method that does not exist.


Common pitfalls

  • Only one half registered. Renderer without transform → unvalidated passthrough into meta. Transform without a renderer → the app cannot draw the field. You usually need both, on both sides of the wire.
  • {type} mismatch. The filter is keyed on the lowercased field_type. If your Special handling configuration produces a different string than your add_filter slug, the filter never fires and the field passes through silently.
  • Three arguments, not four. The live filter passes $value, $submitted_value, $field and expects a returned value, not a returned field array. The older reference doc’s four-arg ($field_data, $field, $entry, $form) shape does not match the code.
  • Don’t edit mam-main. Register from a sibling plugin or mu-plugin so updates don’t clobber you.

Verification

This article was verified against:

  • mam-main/includes/forms-manager/form-data-manager.phpmap_values_to_fields(), the default case firing mam_form_manager_process_field_type_{type} with three arguments.
  • mam-main/includes/forms-manager/submit-app-form.php — the mam_for_gravity_forms_form_submitted_{form_id} filter and per-slug update_post_meta persistence.
  • mam-main/includes/forms-manager/form-cache-manager.php — confirming the in-memory cache model (no invalidate()).
  • mam-main/includes/forms-manager/forms-manager-main.php — the mam_gf_save_special_handling AJAX action that stores per-field special handling.

Re-verify whenever the field-value dispatch in map_values_to_fields() changes, the submit handler signature changes, or the Special handling UI that assigns custom field types is reorganized.


  • Plugin: mam-main — forms manager overview
  • Hook: mam_form_manager_process_fieldtype{type}
  • Hook: mam_for_gravity_forms_formsubmitted{id}
  • Plugin: mam-contact-form (a worked example of the form submission response contract)
Was this article helpful?
Contents

    Need Support?

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