Recipe: Route a web submission to a custom post type

Goal

When a user submits a Gravity Form on the WordPress site (not in the app), create or update a custom-post-type post, save the submitted values as post meta keyed by your own slugs, and call a feature handler with the resulting post ID. After this recipe, web submissions will produce the same content shape as app submissions, so a single downstream feature plugin can consume both flows.

This is the path that mam-special-offers and mam-geodirectory use to back their “edit on web, edit in app” flows.


Prerequisites

  • The form is wired into the app per Recipe: Add a Gravity Form to your app
  • You can write and deploy a small custom plugin (or a functions.php snippet) — this is a developer recipe, not an admin one
  • A target custom post type already registered (or you’re comfortable registering one)
  • The form’s Form ID (e.g. 42)
  • A list of the GF field IDs you want to expose, paired with the post-meta slugs you want them stored under

How the flow works

The plugin listens to gform_after_submission. On every web submission it does the following:

  1. Gate. It checks has_filter( 'mam_for_gravity_forms_web_form_result_form_{form_id}' ). If no filter is registered for this form ID, it exits silently. No consumer = no work.
  2. Look up the field mapping. It calls apply_filters( 'mam_gf_get_form_settings', [] ) and finds the entry whose id option resolves to this submission’s form_id. That entry tells the plugin: which CPT to use, which fields make up post_title, which make up post_content, and the slug → GF-field-index mapping.
  3. Map values. It walks the mapping, reading values out of $entry by GF field index and writing them to $values[$index]['value'] keyed by your slug.
  4. Create or update the post. It builds $post_title and $post_content from the configured fields, sets post_type to the configured CPT, and either inserts a new post or updates an existing one (if post-id is among the slugs and is non-empty, or if $_REQUEST['post_id'] is set).
  5. Save meta. It calls update_post_meta( $post_id, $value['slug'], $value['value'] ) for every mapped value.
  6. Dispatch. It calls apply_filters( 'mam_for_gravity_forms_web_form_result_form_{form_id}', $form_id, $post_id ) so your consumer can finish the job (set status, geocode, send a custom email, etc.).

The pivotal data structure is the mam_gf_get_form_settings entry. Get its shape right and the rest works.


Steps

1. Register your form mapping

Add a callback to mam_gf_get_form_settings. The callback returns an array of “form settings” entries, each describing one form your code knows about.

add_filter( 'mam_gf_get_form_settings', 'my_app_register_offers_form' );

function my_app_register_offers_form( $settings ) {
    $root = 'my_app_offers_';
    $slug = 'offers_form';

    $fields = [
        [ 'id' => 1, 'slug' => 'offer-title',  'title' => 'Offer Title',  'type' => 'text' ],
        [ 'id' => 2, 'slug' => 'description',  'title' => 'Description',  'type' => 'textarea' ],
        [ 'id' => 3, 'slug' => 'start-date',   'title' => 'Start Date',   'type' => 'date' ],
        [ 'id' => 4, 'slug' => 'end-date',     'title' => 'End Date',     'type' => 'date' ],
        [ 'id' => 5, 'slug' => 'post-id',      'title' => 'Offer ID',     'type' => 'hidden' ],
    ];

    // Each field's option key holds the GF field index for THIS site's form.
    // The admin (or your install routine) sets these once.
    foreach ( $fields as $field ) {
        update_option( $root . $slug . '_' . $field['slug'], $field['id'] );
    }

    $settings[] = [
        'category'    => 'general',
        'title'       => __( 'My App: Offers', 'my-app' ),
        'variable'    => 'none',
        'type'        => 'gravity_form',
        'id'          => $root . $slug,                 // option key holding the GF form ID
        'environment' => 'backend',
        'data'        => [
            'cpt'          => 'offer',                  // ← the CPT to write to
            'post_title'   => [ 'offer-title' ],        // ← which slugs build post_title
            'post_content' => [ 'description' ],        // ← which slugs build post_content
            'fields'       => $fields,
        ],
    ];

    return $settings;
}

Two things to notice:

  • The id is an option key, not a form ID. The plugin reads get_option( $entry['id'] ) and compares the result to the GF form_id of the incoming submission. Whoever installs your plugin must set that option to the correct GF form ID — typically through your own admin settings page, or update_option( 'my_app_offers_offers_form', 42 ) in an install routine.
  • Each field’s GF index is also stored in an option ({root}{slug}_{field-slug}). The same install routine sets these. The values you update_option() to should be the actual GF field IDs in the form on this site.

2. Set the form ID and field indexes for this install

If you’re shipping a feature plugin, expose an admin settings page that lets the site admin pick the form and map fields. If you’re writing this for one site, just set the options directly:

update_option( 'my_app_offers_offers_form',                  42 );  // GF form ID
update_option( 'my_app_offers_offers_form_offer-title',      1 );   // GF field IDs
update_option( 'my_app_offers_offers_form_description',      2 );
update_option( 'my_app_offers_offers_form_start-date',       3 );
update_option( 'my_app_offers_offers_form_end-date',         4 );
update_option( 'my_app_offers_offers_form_post-id',          5 );

These can be inspected and edited from Tools → Options if you’ve enabled the options page, or from WP-CLI with wp option get.

3. Subscribe to the result filter

Add a callback to mam_for_gravity_forms_web_form_result_form_{form_id}. The plugin calls this after the post is created or updated and meta is saved. Your callback receives the form ID and the post ID; it returns the form ID (the return value is ignored by the plugin, but you must return something because apply_filters() is the dispatcher — see TD-GF-005).

add_filter( 'mam_for_gravity_forms_web_form_result_form_42', 'my_app_offer_submitted', 10, 2 );

function my_app_offer_submitted( $form_id, $post_id ) {
    // Post is created/updated and meta is saved by mam-gravity-forms-manager.
    // Do anything that should happen *after* — set post status, geocode an address,
    // send a custom email, mark the user as having submitted, etc.

    if ( ! get_post( $post_id ) ) {
        return $form_id;
    }

    // Example: pull a value back out of the meta we just stored.
    $title = get_post_meta( $post_id, 'offer-title', true );

    // Example: trigger your own pipeline.
    do_action( 'my_app_offer_submitted', $post_id, $title );

    return $form_id;
}

4. (Optional) Allow the form to update an existing post

If your form should be able to edit a post that already exists — say, an admin re-edit flow — include the post ID in the submission. Two ways:

  • Add a hidden field to the GF form, mapped to the post-id slug. The plugin sees $values['post-id']['value'] and uses it as the update target.
  • Pass ?post_id=123 on the URL when serving the form. The plugin reads $_REQUEST['post_id'] and runs absint() on it before use.

Either method causes wp_update_post() instead of wp_insert_post().

5. Verify

  1. Submit the GF form on the WP site.
  2. In WP admin, open the CPT list (Offers, in the example above) and confirm a new post appears with the title built from your post_title slugs and the content from your post_content slugs.
  3. Open the post and scroll to Custom Fields — confirm each mapped slug is stored as a meta key with the submitted value.
  4. Confirm your mam_for_gravity_forms_web_form_result_form_{form_id} callback ran (add a temporary mamdebug line, or check whatever your callback’s side effects are).
  5. Re-submit with ?post_id={existing-id} on the URL and confirm the existing post updates rather than a new one being created.

Common gotchas

  • The gate is has_filter(). If no callback is attached at any priority, the entire after_submission body short-circuits. If your post isn’t being created, first check that your add_filter() line is actually running (a typo in the form ID is the usual culprit).
  • The id in mam_gf_get_form_settings is an option key, not a form ID. This is the most common confusion when reading existing consumer code. Search for update_option( $root . $slug, ... ) calls to see how the option gets populated.
  • mam_gf_get_form_settings runs twice in the same submission. The plugin applies the filter once to read the field mapping and a second time to read the CPT/title/content composition. Make sure your callback is idempotent and cheap — see Hook: mam_gf_get_form_settings.
  • update_post_meta is called for every mapped slug, including post-id. That means the post ID is also stored in its own post meta. Harmless but worth knowing if you grep for orphan meta.
  • apply_filters(), not do_action(). The dispatch hook uses apply_filters() even though it’s used for side effects. Always return $form_id from your callback so a future migration to do_action() (planned in TD-GF-005) won’t break.
  • App submissions don’t trigger this flow. App submissions go through mam_form_manager_send_notifications, not gform_after_submission. If you need to react to both, register a callback on each.

Variations

Compose post_title from multiple fields

post_title accepts an array of slugs. The plugin concatenates the values with spaces and trims the result. Example: 'post_title' => [ 'first-name', 'last-name' ].

Use the same callback for multiple forms

Register the same callback against each form’s ID. The plugin passes $form_id as the first argument so you can branch:

add_filter( 'mam_for_gravity_forms_web_form_result_form_42', 'my_app_dispatch', 10, 2 );
add_filter( 'mam_for_gravity_forms_web_form_result_form_43', 'my_app_dispatch', 10, 2 );

function my_app_dispatch( $form_id, $post_id ) {
    switch ( (int) $form_id ) {
        case 42: // Offers
            // …
            break;
        case 43: // Listings
            // …
            break;
    }
    return $form_id;
}

Verification

This article was last verified against:

  • Plugin: mam-gravity-forms-manager v2.3
  • Source: mam-gravity-forms-manager/includes/submit-web-form.php
  • Reference consumer: mam-special-offers/includes/mam_special_offers_form_manager.php (mam_gf_get_form_settings())

Re-verify whenever the gform_after_submission handler in mam_gf_submit_web_form::after_submission() changes the order of post creation, meta save, and result dispatch; the mam_gf_get_form_settings entry shape (id, data.cpt, data.post_title, data.post_content, data.fields) changes; or the $_REQUEST['post_id'] precedence over the post-id mapped field changes.


  • Plugin overview: mam-gravity-forms-manager
  • Recipe: Activate the plugin
  • Recipe: Add a Gravity Form to your app
  • Hook: mam_gf_get_form_settings
  • Hook: mam_for_gravity_forms_web_form_resultform{form_id}

Metadata

Field Value
Article type Recipe (Developer)
Plugin slug mam-gravity-forms-manager
Applies to plugin version 2.3+
Category Extending MAM Suite
Audience PHP developer
Estimated time 30 minutes
Prerequisites article Recipe: Add a Gravity Form to your app
Last verified 2026-05-01
Contents

    Need Support?

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