Register Form

You can register a form in the “ACF > Forms” menu or using acfe_register_form() as below. See Settings Cheatsheet for the list of all available arguments.

Usage example:

functions.php
add_action('acfe/init', 'my_form_register');
function my_form_register(){

    acfe_register_form(array(
        'name' => 'my-form',
        'title' => 'My Form',
        'field_groups' => array(
            'group_5ffd27b7d0c62',
        ),
        'attributes' => array(
            'submit' => array(
                'value' => 'Submit Form',
            ),
        ),
        'success' => array(
            'message' => 'The page "{action:post:post_title}" has been created.',
        ),
        'actions' => array(

            array(
                'action' => 'post',
                'type'   => 'insert_post',
                'save'   => array(
                    'post_type'    => 'page',
                    'post_status'  => 'publish',
                    'post_title'   => '{field:field_5ff7ee7cdd8f3}',
                    'acf_fields'   => array(
                        'field_6102a25520748',
                        'field_63efcca4ea9b8',
                    ),
                ),
            ),

        ),
    ));

}

Render Form

Render with form name

Once registered, the form can be rendered anywhere on the front-end using the acfe_form() function, with the form name as argument:

page-my-form.php
<?php get_header(); ?>
    <?php acfe_form('my-form'); ?>
<?php get_footer(); ?>

Render with form id

Alternatively, you can use the form ID, in case the form was registered with the Form UI:

page-my-form.php
<?php get_header(); ?>
    <?php acfe_form(120); ?>
<?php get_footer(); ?>

Render and override settings

You can also render a form and override settings on the fly. To do so, pass an array with a name or ID to choose which form to display, and override existing settings. Usage example:

page-my-form.php
<?php get_header(); ?>

    <?php
    acfe_form(array(
        'name'    => 'my-form',      // render 'my-form'
        'success' => array(
            'message' => 'Success!', // change the success message
        ),
    ));
    ?>

<?php get_footer(); ?>

Inline Form

Alternatively, it is possible to register and render an inline form, directly within the front-end template. The following form doesn’t require acfe_register_form(), as it is created and displayed on the fly:

page-inline-form.php
<?php get_header(); ?>

    <?php
    
    // create & render an inline form
    acfe_form(array(
        'name' => 'inline-form',
        'title' => 'Inline Form',
        'field_groups' => array(
            'group_5ffd27b7d0c62',
        ),
        'attributes' => array(
            'submit' => array(
                'value' => 'Submit',
            ),
        ),
        'success' => array(
            'message' => 'The page "{action:post:post_title}" has been created.',
        ),
        'actions' => array(

            array(
                'action' => 'post',
                'type'   => 'insert_post',
                'save'   => array(
                    'post_type'    => 'page',
                    'post_status'  => 'publish',
                    'post_title'   => '{field:field_5ff7ee7cdd8f3}',
                    'acf_fields'   => array(
                        'field_6102a25520748',
                        'field_63efcca4ea9b8',
                    ),
                ),
            ),

        ),
    ));
    
    ?>

<?php get_footer(); ?>

Render Fields

The most basic way to display field groups is to fill the field_groups argument. The form will automatically display the related fields.

acfe_form(array(
    'name' => 'my-form',
    'field_groups' => array(
        'group_5ffd27b7d0c62',
        'group_5ffd66b8aa7ca',
    ),
));

If you need to control where the fields are displayed you can use it in combination with the render argument, and the {render:fields} template tag.

The following example will render both field groups within arbitrary HTML:

acfe_form(array(
    'name'   => 'my-form',
    'field_groups' => array(
        'group_5ffd27b7d0c62',
        'group_5ffd66b8aa7ca',
    ),
    'render' => '
        <h3>My Form</h3>
        <div class="wrapper">
            {render:fields}
        </div>
    ',
));

As an alternative, it is possible to omit the field_groups argument, and directly use {render:group_5f5e3b3b1b1d1}. The following example will split the displayed field groups:

acfe_form(array(
    'name'   => 'my-form',
    'render' => '
        <h3>Field Group 1</h3>
        <div class="wrapper">
            {render:group_5ffd27b7d0c62}
        </div>

        <h3>Field Group 2</h3>
        <div class="wrapper">
            {render:group_5ffd66b8aa7ca}
        </div>
    ',
));

It is also possible to render each fields individually, using the field name or key. Please note that you can only render top-level fields.

For example, you cannot render Repeater sub fields individually, you have to render the whole repeater. If you need to hide a sub field, please see the Hide Field section.

acfe_form(array(
    'name'   => 'my-form',
    'render' => '
        <div class="row">
            <div class="col-6">
                {render:my_field}
            </div>
            <div class="col-6">
                {render:field_5ffa85c208d1e}
            </div>
            <div class="col-12">
                {render:field_5ff29c5ba9df9}
            </div>
        </div>
    ',
));

Render Submit

The submission button is rendered after all the fields by default, but it can be also placed manually with the {render:submit} tag. Note the submit attribute must still be enabled and filled.

acfe_form(array(
    'name'   => 'my-form',
    'render' => '
        <h3>My Form</h3>
        <div class="wrapper">
            {render:submit}
            {render:field_5ff7ee7cdd8f3}
            {render:field_5ffa85c208d1e}
        </div>
    ',
));

Prefill Fields

Fields can be prefilled with values using the map argument. Assigned values must be unformatted and compatible with their field type. For example, a Date Picker field value should use the YYYYMMDD format.

acfe_form(array(
    'name'   => 'my-form',
    'render' => '
        {render:field_5ff7ee7cdd8f3}
        {render:field_5ffa85c208d1e}
    ',
    'map' => array(
        'field_5ff7ee7cdd8f3' => array(
            'value' => 'my value',
        ),
        'field_5ffa85c208d1e' => array(
            'value' => 'my other value',
        ),
    ),
));

Here is an example with a more complex field, like a Repeater:

acfe_form(array(
    'name'   => 'my-form',
    'render' => '
        {render:field_5ff7ee7cdd8f3}
    ',
    'map' => array(

        // repeater field
        'field_5ff7ee7cdd8f3' => array(
            'value' => array(
                array(
                    'field_621a65cc5521e' => 'Row 1',
                ),
                array(
                    'field_621a65cc5521e' => 'Row 2',
                ),
            )
        ),

    ),
));

If you need to inject values from existing fields in the database, make sure to get unformatted values. To do so, add false as the last argument in your get_field(). See documentation.

// get value from existing post (unformatted)
$my_field = get_field('my_field', 145, false);

acfe_form(array(
    'name'   => 'my-form',
    'render' => '
        {render:field_5ff7ee7cdd8f3}
    ',
    'map' => array(
        'field_5ff7ee7cdd8f3' => array(
            'value' => $my_field, // use value
        ),
    ),
));

Hide Fields

The map argument allows to hide specific fields by assigning false, just like in the acf/prepare_field hook.

acfe_form(array(
    'name'   => 'my-form',
    'render' => '
        {render:group_5ffd27b7d0c62}
    ',
    'map' => array(
        'field_5ff7ee7cdd8f3' => false, // hide this field
    ),
));

Alter Fields

Following this logic, we can also change the label, instructions and other field settings:

acfe_form(array(
    'name'   => 'my-form',
    'render' => '
        {render:field_5ff7ee7cdd8f3}
    ',
    'map' => array(
        'field_5ff7ee7cdd8f3' => array(
            'label'        => 'New Label',
            'instructions' => 'New Instructions',
            // ...
        ),
    ),
));

Validation

The easiest way to validate the whole form is to use the acfe/form/validate_form hook. You can retrieve any field input value using get_field() and omit the $post_id argument.

To throw an error, simply use the acfe_add_validation_error() helper.

functions.php
add_action('acfe/form/validate_form/form=my-form', 'my_form_validation');
function my_form_validation($form){
    
    // get field input value
    $my_field = get_field('my_field');

    // get field input value (unformatted)
    // $my_field = get_field('my_field', false, false);
    
    // check field value
    if($my_field === 'my value'){
        
        // add validation error
        acfe_add_validation_error('my_field', 'my value is not allowed');
        
    }
    
}

Indeed, the native ACF hook acf/validate_value will also work for front-end forms. However, you have to make sure the request comes from the front-end, and not the WP Admin, using acfe_is_front().

functions.php
add_filter('acf/validate_value/name=my_field', 'my_acf_validate_value', 10, 4);
function my_acf_validate_value($valid, $value, $field, $input_name){

    // bail early if not on the front-end
    if(!acfe_is_front()){
        return $valid;
    }

    // ...

    return $valid;

}

Submission

Following the same logic as the validation, you can retrieve field input values with get_field() inside the acfe/form/submit_form hook. See documentation.

In this example, we’ll create a new page using fields values, and update ACF fields in the new page with update_field().

page-my-form.php
<?php get_header(); ?>

    <?php
    acfe_form(array(
        'name' => 'my-form',
        'title' => 'My Form',
        'field_groups' => array(
            'group_5ffd27b7d0c62',
        ),
    ));
    ?>

<?php get_footer(); ?>
functions.php
add_action('acfe/form/submit_form/form=my-form', 'my_form_submission');
function my_form_submission($form){

    // get title input value
    $title = get_field('title');

    // get description input value
    $description = get_field('description');

    // create page
    $post_id = wp_insert_post(array(
        'post_title'   => $title,       // use title field
        'post_content' => $description, // use description field
        'post_type'    => 'page',
        'post_status'  => 'publish',
    ));
    
    // make sure the page was created
    if($post_id && !is_wp_error($post_id)){
        
        // get fields input value
        // we must get unformatted values in order to update fields later
        $date = get_field('date', false, false);
        $checkbox = get_field('checkbox', false, false);
        
        // update acf fields on the new page
        update_field('date', $date, $post_id);
        update_field('checkbox', $checkbox, $post_id);

    }
    
}

Submission with Action

The exact same result can be achieved using the built-in Post Action, with less code. Usage example:

page-my-form.php
<?php get_header(); ?>

    <?php

    acfe_form(array(
        'name' => 'my-form',
        'title' => 'My Form',
        'field_groups' => array(
            'group_5ffd27b7d0c62', // display field group
        ),
        'actions' => array(

            array(
                'action' => 'post',
                'type'   => 'insert_post',
                'save'   => array(
                    'post_type'    => 'page',
                    'post_status'  => 'publish',
                    'post_title'   => '{field:field_5ff7ee7cdd8f3}', // title field
                    'post_content' => '{field:field_621a65cc5521e}', // description field
                    'acf_fields'   => array(
                        'field_6102a25520748', // date field
                        'field_63efcca4ea9b8', // checkbox field
                    ),
                ),
            ),

        ),
    ));

    ?>

<?php get_footer(); ?>

Conditional Render

The easiest way to control whenever a form should be rendered is to use the classic if() conditional logic.

page-my-form.php
// only display the form to logged user
if(is_user_logged_in()){
    acfe_form('my-form');
}

Alternatively, you can use the acfe/form/load_form hook, check your condition and return false to hide the form. This example will achieve the same result, but without the if() condition on the front-end template.

functions.php
add_filter('acfe/form/load_form/form=my-form', 'my_form_load');
function my_form_load($form){
    
    // check if user is logged in
    if(!is_user_logged_in()){
        return false; // hide form
    }
    
    // display form
    return $form;
    
}
page-my-form.php
<?php get_header(); ?>
    <?php acfe_form('my-form'); ?>
<?php get_footer(); ?>

Pass Custom Data

You can pass custom data to the form during the form render, and retrieve it later in the Form UI or in PHP hooks. Usage example:

page-my-form.php
acfe_form(array(
    'name'     => 'my-form',
    'user_id'  => get_current_user_id(), // add custom data
    'success'  => array(
        'message' => 'Success',
    ),
));

Custom data can also be passed with the acfe/form/load_form filter. See documentation. Usage example:

functions.php
add_filter('acfe/form/load_form/form=my-form', 'my_form_settings');
function my_form_settings($form){
    
    // add custom data
    $form['user_id'] = get_current_user_id();
    
    // return
    return $form;
    
}

These custom data can then be retrieved in any front-end forms hooks, or from the Form UI using the {form:user_id} tag. Usage example:

functions.php
// form submission
add_action('acfe/form/submit_form/form=my-form', 'my_form_submit');
function my_form_submit($form){
    
    // retrieve custom data
    $user_id = $form['user_id'];

    // ...
    
}