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-mainactive, 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 editingmam-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:
{type}is the field’sfield_typestring, lowercased. That is the dynamic segment in the filter name.- 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 themam_gf_save_special_handlingAJAX action informs-manager-main.php), which is emitted into the form JSON as the field’sspecial_handling. The app maps that code to a renderer and to thefield_typestring 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_responseand ultimately the app’s submission response. Return an array in the same shapestandard-form-handler.phpproduces:status,type,message,process_normal, andsend_notifications.submit-app-form.phpreadsprocess_normalandsend_notificationsoff 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 lowercasedfield_type. If your Special handling configuration produces a different string than youradd_filterslug, the filter never fires and the field passes through silently.- Three arguments, not four. The live filter passes
$value, $submitted_value, $fieldand 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.php—map_values_to_fields(), thedefaultcase firingmam_form_manager_process_field_type_{type}with three arguments.mam-main/includes/forms-manager/submit-app-form.php— themam_for_gravity_forms_form_submitted_{form_id}filter and per-slugupdate_post_metapersistence.mam-main/includes/forms-manager/form-cache-manager.php— confirming the in-memory cache model (noinvalidate()).mam-main/includes/forms-manager/forms-manager-main.php— themam_gf_save_special_handlingAJAX 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.
Related
- 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)
