Written by John Reed
Developing custom post types for WordPress extends a seemingly simple blogging platform into a fully-fledged content management system. Adding the Advanced Custom Fields (ACF) plugin to the mix allows for the easy creation of metadata for these post types, but as the number of fields grows, usability becomes a genuine concern.
We recently developed an intake form for a client that mapped to a custom post type with hundreds of fields, so we needed to give end users a way to save their progress (making the questionnaire easier to digest) and allow admins to manage how these fields were presented. This is how we did it using the powerful
In addition to having the ACF PRO plugin installed, this demo will utilize a custom post type called “Application” created using
register_post_type() as well as reference custom theme options created and stored using the
Field groups for our custom post type were added and organized by subject area for the intake questionnaire.
For our theme options, we created an ACF Repeater with two sub-fields: a simple text field for the form/page title and a textarea to store the field group IDs, which we’ll use to reference the custom fields from our custom post type. This demo uses just one field group per page, but using a textarea provides the flexibility to have multiple field groups per page (we will explode the textarea into an array when we code out the form).
We will retrieve the intake pages established above in our template to start building our UI. While acf_form() will do the heavy lifting to generate our form, we also need to include acf_form_head() at the top of our page template to enqueue all ACF-related scripts and styles for the form to display correctly.
<?php // get intake pages to display $intake_pages = get_field( 'intake_pages', 'option' ) ?: ; // post_id or 'new_post' $post_id = isset( $_GET['post_id'] ) ? $_GET['post_id'] : 'new_post'; // ACF form head acf_form_head(); // our theme header get_header(); ?>
Next, let’s build the tabs for our intake form:
<ul class="nav nav-pills"> <?php foreach( $intake_pages as $i => $intake_page ): // convert the title to a sanitized slug $slug = sanitize_title( $intake_page['title'] ); // build the href with
post_id and stepparams $href = site_url( sprintf( '?post_id=%s&step=%s', $post_id, $slug ) ); // active state $active = ( !isset( $_GET['step'] ) && !$i ) || $_GET['step'] === $slug; ?> <li class="nav-item"> <a class="nav-link<?php echo $active ? ' active' : ''; ?>" href="<?php echo $href; ?>"> <?php echo $intake_page['title']; ?> </a> </li> <?php // only display the first tab for new entries if( 'new_post' === $post_id ) break; endforeach; ?> </ul>
Now, we can build our form:
<?php while ( have_posts() ) : the_post(); foreach( $intake_pages as $i => $intake_page ): // convert the title to a sanitized slug $slug = sanitize_title( $intake_page['title'] ); // build the href with
post_id and stepparams $href = site_url( sprintf( '?post_id=%s&step=%s', $post_id, $slug ) ); // active state $active = ( !isset( $_GET['step'] ) && !$i ) || $_GET['step'] === $slug; // prevent multiple forms from rendering on single page view if( ! $active ) continue; // convert our saved textarea value into an array $field_groups = explode( "\r\n", $intake_page['page_field_groups'] ); // get next page $next = next( $intake_pages ); // build the return URL $return = site_url( sprintf( '?post_id=%s&step=%s', $post_id, $next ? sanitize_title( $next['title'] ) : $slug ) ); // generate our form acf_form( [ 'id' => sprintf( '%s-form', $slug ), 'post_id' => $post_id, 'field_groups' => $field_groups, 'new_post' => [ 'post_type' => 'application', 'post_status' => 'publish', ], 'return' => $return, ] ); endforeach; endwhile; ?>
acf_form(), two use cases
The magic of this function lies in the
new_post parameters. If
post_id is an integer, the ACF plugin will know we are editing a specific post and will update that post when the form is submitted. When
post_id is set to ‘new_post’, the
new_post parameter will be used to establish the baseline parameters for this post and accepts all of the options as
wp_insert_post() does. Pretty cool!
Caveats and final thoughts
It should be noted that passing a
post_id as a query parameter has security implications. For the sake of simplicity, this demo does not check that the current user has permission to edit the specified post, but you’ll definitely want to do so in your application. That said, with a bit of setup and a couple of loops, you can see the power of the
acf_form() function. This simple approach allowed our client to create a 10-step intake form with hundreds of custom fields, all without writing a single
<input> tag. If you like what you see, contact us for help setting up a multi-step form on your website!