Having ACF-like repeater fields on Gravity Forms can be a bit tricky. Up until recently there was a free plugin which did the job, but, according to its author, “it is no longer maintained and is probably broken”. Currently, the best plug-n’-play alternative seems to be Gravity Forms Nested Forms, which is part of the paid Gravity Perks.

The good news, though, is that there is another, free and official alternative: Since version 2.4, Gravity Forms includes native support for repeater fields. Sadly, this feature seems to be stuck in beta for too long, with no estimate on when it will be ready for prime time. So, currently there is no Form Editor UI component built for this field yet, which means that you cannot add it to a form using standard Form Editor drag and drop methods. Practically, this field is only intended for developers who can build their forms programmatically, or through other methods.

The official documentation includes usage examples, demonstrating how you can implement the functionality on your forms. When I tried implementing them, though, undocumented issues came up and no workarounds could be found on Google, Stack Overflow or the official Gravity Forms Forum. Lets take a closer look at the official example:

// Adjust your form ID
add_filter( 'gform_form_post_get_meta_149', 'add_my_field' );
function add_my_field( $form ) {
 
    // Create a Single Line text field for the team member's name
    $name = GF_Fields::create( array(
        'type'   => 'text',
        'id'     => 1002, // The Field ID must be unique on the form
        'formId' => $form['id'],
        'label'  => 'Name',
        'pageNumber'  => 1, // Ensure this is correct
    ) );
 
    // Create an email field for the team member's email address
    $email = GF_Fields::create( array(
        'type'   => 'email',
        'id'     => 1001, // The Field ID must be unique on the form
        'formId' => $form['id'],
        'label'  => 'Email',
        'pageNumber'  => 1, // Ensure this is correct
    ) );
 
    // Create a repeater for the team members and add the name and email fields as the fields to display inside the repeater.
    $team = GF_Fields::create( array(
        'type'             => 'repeater',
        'description'      => 'Maximum of 3 team members  - set by the maxItems property',
        'id'               => 1000, // The Field ID must be unique on the form
        'formId'           => $form['id'],
        'label'            => 'Team Members',
        'addButtonText'    => 'Add team member', // Optional
        'removeButtonText' => 'Remove team member', // Optional
        'maxItems'         => 3, // Optional
        'pageNumber'       => 1, // Ensure this is correct
        'fields'           => array( $name, $email ), // Add the fields here.
    ) );
 
    $form['fields'][] = $team;
 
    return $form;
}
 
// Remove the field before the form is saved. Adjust your form ID
add_filter( 'gform_form_update_meta_149', 'remove_my_field', 10, 3 );
function remove_my_field( $form_meta, $form_id, $meta_name ) {
 
    if ( $meta_name == 'display_meta' ) {
        // Remove the Repeater field: ID 1000
        $form_meta['fields'] = wp_list_filter( $form_meta['fields'], array( 'id' => 1000 ), 'NOT' );
    }
 
    return $form_meta;
}

The filter on line #2 takes the results of the add_my_field() function and passes it to the gform_form_post_get_meta_{form_id}, which is executed when the form meta is retrieved ({form_id} should be replaced by the id of the form that you want to add the repeater on). The problem here is that with that function alone, every time that you edit your form’s fields from the backend and hit “save”, a new repeater field gets created.

(this could go on forever)

That’s why we have the second function of the example: remove_my_field() removes the field before the form is saved while add_my_field() adds it, guaranteeing that way that the field gets added only once. Problem solved? – Well, not so fast. Removing the existing reapeater field and then creating it again prevents you from moving it wherever you like. So, unless you are OK by always having your repeater field last, this example would cause you trouble. As you can see on the following gif, using the remove_my_field() function prevents the field-copying issue, but now you can’t reorder the fields.

(every time you reorder the repeater field, hitting “update” will drag it back at the end of the form).

So, let see how we could deal with those issues and do some more refactoring of the form along the way. First, we create our function based on the official example:

$form_id = 3; 
add_filter( 'gform_form_post_get_meta_' . $form_id, 'add_repeater_field' ); 
function add_repeater_field( $form ) { 

  $fields = [];
  $repeater = GF_Fields::create(
    [
      'type'             => 'repeater',
      'description'      => __( 'Maximum of 3 team members - set by the maxItems property', 'theme-prefix' ),
      'id'               => $field_id,
      'formId'           => $form['id'],
      'label'            => __( 'Team Members', 'theme-prefix' ),
      'addButtonText'    => __( 'Add team member', 'theme-prefix' ),
      'removeButtonText' => __( 'Remove team member', 'theme-prefix' ),
      'maxItems'         => 5,
      'pageNumber'       => 1,
      'fields'           => $fields,
    ]
  );

  return $form; 
}

As you can see, I prefer having the form ID on a variable called $form_id. This is more convenient because you might need to use the same ID elsewhere on your code and it makes reading the code easier. In the end, though, it is a matter of personal preference. Then, We have to call the repeater field and return the form. The only actual difference compared to the official example is that instead of creating the fields as separate objects, we put them inside a $fields array. That way, everything inside the $fields array will be included on our repeater automatically. So, let’s add some fields:

$form_id = 3; 
add_filter( 'gform_form_post_get_meta_' . $form_id, 'add_repeater_field' ); 
function add_repeater_field( $form ) { 
  
  $field_id = 1000;

  $fields = [
    GF_Fields::create(
        [
          'type'       => 'text',
          'id'         => $field_id += 1,
          'formId'     => $form['id'],
          'label'      => __( 'Field 1', 'theme-prefix' ),
          'pageNumber' => 1,
        ]
      ),
      GF_Fields::create(
        [
          'type'       => 'text',
          'id'         => $field_id += 1,
          'formId'     => $form['id'],
          'label'      => __( 'Field 2', 'theme-prefix' ),
          'pageNumber' => 1,
        ]
      ),
      GF_Fields::create(
      [
        'type'       => 'select',
        'id'         => $field_id += 1,
        'formId'     => $form['id'],
        'label'      => __( 'Field 3 (dropdown)', 'theme-prefix' ),
        'choices'    => [
          [
            'text'       => 'Option 1',
            'value'      => 'option_1',
            'isSelected' => true,
          ],
          [
            'text'       => 'Option 2',
            'value'      => 'option_2',
            'isSelected' => false,
          ],
          [
            'text'       => 'Option 3',
            'value'      => 'option_3',
            'isSelected' => false,
          ],
        ],
        'pageNumber' => 2,
      ]
    ),
  ];
  $repeater = GF_Fields::create(
    [
      'type'             => 'repeater',
      'description'      => __( 'Maximum of 3 team members - set by the maxItems property', 'theme-prefix' ),
      'id'               => $field_id,
      'formId'           => $form['id'],
      'label'            => __( 'Team Members', 'theme-prefix' ),
      'addButtonText'    => __( 'Add team member', 'theme-prefix' ),
      'removeButtonText' => __( 'Remove team member', 'theme-prefix' ),
      'maxItems'         => 5,
      'pageNumber'       => 1,
      'fields'           => $fields,
    ]
  );

  return $form; 
}

One thing that I didn’t like on the official example was that you had to manually add each field’s id by hand. So, to avoid that, we create a $field_id variable and we give it a high enough number (like 1000). Then, on each field, we call $field_id +=1 to increment that number. As a bonus, along with the text field I’ve included an example of a dropdown.

What we need to do now is to check if the repeater field already exists on the form. If it isn’t, we add it, otherwise we leave the form intact. We do so by adding the following lines:

$repeater_exists = false;
foreach ( $form['fields'] as $field ) {
  if ( 'repeater' === $field->type && $field->id === $field_id ) {
    $repeater_exists = true;
  }
}
if ( ! $repeater_exists ) {
  $form['fields'][] = $repeater;
}

The first line creates a $repeater_exists variable which checks for the existence of the fields and is by default set to false. Then, we use a foreach loop to see if a repeater field with the same id already exists on our form. If it does, we set the variable’s value to true. When it all ends, we check the value of the variable and if it isn’t set to true (meaning that the repeater field does not exist on the form), we add our repeater field to the form.

Eventually, our complete code should look like that:

Bonus tip: One more annoying thing about Gravity Forms repeater field is that when the user hits the remove button to delete a field that they previously added, it pops-up an ugly JavaScript alert:

In principle, this isn’t bad. On the contrary, it is a good practice to warn users when they are about to delete something. This alert, though, is ugly, not customizable and not translatable. Also, currently there isn’t a hook or filter to remove or modify it. So, if you want to remove it you will have to add the following jQuery code somewhere on your custom scripts:

$(document).on('click', '.add_repeater_item', function () {
  $('.remove_repeater_item').attr('onclick', 'gformDeleteRepeaterItem(this)');
});

What it does it that every time the user adds a new field (and, therefore, the need to remove it appears), it will modify the remove button’s onclick event and remove the alert.

 

So far, Gravity Forms’ native repeater field, even on its current beta state, already is a powerful tool, but it is more or less an uncharted territory, with very few documentation available. If you have any experience with it and came up with tips of your own, I would be happy to hear about it.

3 thoughts on “Refactoring the Gravity Forms native repeater field

Leave a Reply

Your email address will not be published. Required fields are marked *