Skip To Content

ACF Flexible Content: Simplifying the Flexible Content Loop

Development

Marek Grabowiecki Client Success Manager

Advanced Custom Fields is a plugin that extends the functionality of WordPress by using custom fields to build websites faster and more flexible. Advanced Custom Fields, also known as ACF, has a field type called Flexible Content that is used to add an unlimited number of layouts which can be reordered and reused to populate your posts/pages with content in a structured manner.

ACF provides documentation that explains how to use their Flexible Content field using something called the Flexible Content Loop. Their Flexible Content Loop is easy to use but we’ve figured out a way to simplify it even further and utilize its features in a more dynamic way; we like to refer to this as simplifying the Flexible Content Loop.

ACF’s Example of How to Use the Flexible Content Field

// check if the flexible content field has rows of data
if( have_rows('flexible_content_field_name') ):

    // loop through the rows of data
    while ( have_rows('flexible_content_field_name') ) : the_row();

        if( get_row_layout() == 'paragraph' ):

            the_sub_field('text');

        elseif( get_row_layout() == 'download' ):

            $file = get_sub_field('file');

        endif;

    endwhile;

else :

    // no layouts found

endif;

In the example above, you’ll notice that in each iteration of the loop we’re checking for the instance of specific layout types. Each layout type has code that is associated with it which will ultimately affect what is output on the front end.

ACF kept this example simple by either echoing the value of a field when the paragraph layout is used or assigning a file to a variable when the download layout is used. In more realistic use cases you would be outputting a lot more markup that is structured around the different layouts types that you’ve created.

Organizing Your Flexible Layouts Into Partials

// check if the flexible content field has rows of data
if( have_rows( 'flexible_content_field_name' ) ):

    // loop through the rows of data
    while ( have_rows( 'flexible_content_field_name' ) ) : the_row();

        if ( get_row_layout() == 'paragraph' ) :

            get_template_part( 'partials/flexible-layouts/flexible', 'paragraph' );

        elseif ( get_row_layout() == 'download' ) :

            get_template_part( 'partials/flexible-layouts/flexible', 'download' );

        endif;

    endwhile;

else :

    // no layouts found

endif;

As mentioned previously, you’re more than likely going to be doing a lot more than just echoing a simple field or assigning a value to a variable within your layouts; you’re going to be building out sections of pages that can be reused throughout the site so that you don’t have to redefine these layout multiple times. These layouts can become quite complex and can reach upwards of a few hundred lines of code. It’s for this reason that we prefer to break out our layouts into their own separate partials.

You’ll notice from the example above that we like to organize our flexible layouts into the partials/flexible-layouts/ directory. Each of our partials begin with flexible- and end in the name of the layout. Having our layouts separated into partials improves the readability of the loop and helps other developers understand what’s happening under the hood. It also makes for better management of our Flexible Content layouts.

Simplifying the Flexible Content Loop

// check if the flexible content field has rows of data
if( have_rows( 'flexible_content_field_name' ) ):

    // loop through the rows of data
    while ( have_rows( 'flexible_content_field_name' ) ) : the_row();

        if ( get_row_layout() == 'paragraph' ) :

            get_template_part( 'partials/flexible-layouts/flexible', 'paragraph' );

        elseif ( get_row_layout() == 'download' ) :

            get_template_part( 'partials/flexible-layouts/flexible', 'download' );

        endif;

    endwhile;

else :

    // no layouts found

endif;

Referring back to the previous code snippet, we can see that within each iteration of the loop we’re checking for every possible layout option. This is fine in the example provided since we’re only looking for 2 layout options; paragraph and download. But let’s say that you’re using the Flexible Content field to build your pages in a flexible nature and you have over 20 layout options available. Your loop has now grown out of control and has become somewhat unmanageable.

We were running into this scenario quite frequently so we came up with a dynamic way to pull in the correct Flexible Content partial by using a simple naming convention that involves matching up our partials to our layouts, one-for-one.

Basically, for each Flexible Content layout option that is created in ACF, an equivalent php partial is created within the theme.

If the Flexible Content field has a layout named flexible-two-column:

flexible content layout name

You would create a partial named flexible-two-column.php within your Flexible Content partials directory:

flexible content partial name

Now, instead of defining each of your layouts within your Flexible Content Loop, you would modify the loop so that it dynamically pulls in the correct partial based on the Flexible Content layout’s name:

// ID of the current item in the WordPress Loop
$id = get_the_ID();

// check if the flexible content field has rows of data
if ( have_rows( 'flexible_layouts', $id ) ) :

    // loop through the selected ACF layouts and display the matching partial
    while ( have_rows( 'flexible_layouts', $id ) ) : the_row();

        get_template_part( 'partials/flexible-layouts/' . get_row_layout() );

    endwhile;

elseif ( get_the_content() ) :

    // no layouts found

endif;

Using the Flexible Content Loop in this manner encourages code reusability and scalability by giving you the power to continually add to your library of Flexible Content layouts with minimal code adjustment.

Thanks!

If you have any questions, are having issues getting this to work with your setup, or are excited that you learned how to simplify the Flexible Content Loop, feel free to leave a comment below!

14 Replies to “ACF Flexible Content: Simplifying the Flexible Content Loop”

  1. This is really good stuff, thanks for sharing. Just FYI, I found this content whilst looking for help so that I could output content by selecting post objects from an ACF field in one of my flexible content rows. I wanted to use it to build default blocks of content, as CPT’s, that I could then pop into any other page, in the flow of the flex content. The problem was, the post object content uses the SAME Flexible Content Fields Groups & the same loop!

    Sounds confusing I know. At first this loop didn’t work as it called in all flex content for the main page as well. By switching to using $postID = $post->ID; instead of get_the_id, your loop is now working perfectly for my purposes. And I love the simplicity & stability of this approach! Thanks again

    1. Hey Mat!

      I’m glad that you found this example useful.

      I think that we’ve used the same approach of building out blocks of content in CPTs that could be used on various pages just like you’re explaining!

      Our setup involved having a CPT called CTA (Call to Actions) which used the same Flexible Content Fields Group to build out blocks of content.

      Our Flexible Content Fields Group included a layout named “Call To Action” which was a Relational Post Object Field Type. This field allowed the user to select a specific CTA post to display on the page. Since the blocks of content were defined on the CTA post itself, the same content could be reused in various places while only having to define it once.

      The Call To Action layout had a field name of “cta” which pulled in the partial called cta.php.

      I put the code that is in this partial into a snippet on BitBucket if you’re interested in checking it out:
      https://bitbucket.org/snippets/nvisionsolutionsdev/GeLr5n

      Thanks again for commenting 🙂

  2. Hey guys! This concept of having a Post Type with Templates that you build into the Flexible Content Field sounds super promising. Only I have still not found the holy grail of outputting nested ACF subfields within the WYSIWYG Templates to get processed through the Flexible Content loop. Do you have any ideas on that? Great work otherwise! Thanks for the input.

    1. Hi Filip – Thanks for your comment!

      If I’m understanding your question correctly, it sounds like you’re looking to build sections (using Flexible Content Layouts) as posts within a Custom Post Type and then pull them in through a Relationship field to be used throughout the site on various posts/pages.

      If the above sounds correct, you should be able to use the steps below to achieve this type of setup:

      1. Create a Custom Post Type labelled “Call to Actions” (which does not have an archive or dedicated singles)

      2. Add a “Call to Actions” section to your Flexible Content Layout Field Group. The only field required in this layout is a relationship field tied to the Call to Actions posts: https://cl.ly/5575df91243b

      3. Add a cta.php partial to your Flexible Layouts directory using the following details as a guide: https://bitbucket.org/snippets/nvisionsolutionsdev/EboMAn

      I hope this helps! Thanks again for the comment 🙂

  3. By creating individual php with the acf Can it help speed up the sites loading and saving time? Right now I am encountering very long loading times which is slowing down my sites that I am creating because of so many Nested fields in the acf.

    1. Hi Nate – thanks for your comment!

      Great question. You might see slightly faster load speeds by breaking your code into partial files (instead of having 1 massive file) but I don’t think you’d see too much of a gain.

      In your case, your load speeds could potentially be a result of unnecessary scripts being loaded, large images (or video files) being requested, or even impacted by your web host.

      My recommendation would be to start by using a few speed test tools to see where you might be able to make improvements to help with your load speeds. These tools will help with identifying whether the issue could be fixed by compressing images and only requesting the sizes that are required, only loading scripts on the pages that they are used on, or looking into a faster web host (I highly recommend checking out WP Engine – or FlyWheel if you’re on a bit of a budget).

      I’ve listed a few of the tools that we like to use below:

      Good luck – I hope this helps!
      Marek

  4. Hi Marek,

    Great post, and your comment above about using a CPT to create reusable CTA blocks is exactly what I was thinking of setting up!

    How would you go about setting a default CTA block if no others are set in a flexible field area? Something like an Options page perhaps?

    Basically, I am currently building a site where each page is likely to have the same CTA block on it. The easy route would be to just hard code it onto the templates, but I’d rather give the client the ability to edit the content on it.

    Setting it up as a CTA (within a CPT) would solve that, but require the client to set it on every single page (and admins can forget things). So was wondering if I could set the page to ‘default’ to a particular CTA if none is set.

    Thanks again!

    1. Hi Rob,

      Thanks for your comment – and great question!

      There are likely a few different ways that you could approach this depending on the specific project/requirements but I would likely go with the route that you mentioned in your last sentence; If there are no CTAs selected in the flexible layouts loop (or any flexible layouts at all) then default to the “default CTA” that you’ve defined within one of your options pages. You would need to write a function to ensure that you’re selecting the correct ID (options ID) or create a separate partial to cover this specific scenario.

      The code would look something like this:

      // ID of the current item in the WordPress Loop
      $id = get_the_ID();
      
      // check if the flexible content field has rows of data
      if ( have_rows( 'flexible_layouts', $id ) ) :
      
          // variable to check if CTA layout selected within the flexible layouts loop
          $has_cta = false;
      
          // loop through the selected ACF layouts and display the matching partial
          while ( have_rows( 'flexible_layouts', $id ) ) : the_row();
      
              get_template_part( 'partials/flexible-layouts/' . get_row_layout() );
      
              // check if current layout is CTA layout
              if ( 'cta' == get_row_layout() ) :
                  $has_cta = true;
              endif;
      
          endwhile;
      
          // show default default CTA layout as none are provided. Alternatively, link to your cta partial and write code to determine if using the post/page id or options page id
          if ( !$has_cta ) :
              get_template_part( 'partials/flexible-layouts/default-cta' );
          endif;
      
      // no flexible layouts provided
      elseif ( get_the_content() ) :
      
          // no layouts found - show default CTA
          get_template_part( 'partials/flexible-layouts/default-cta' );
      
      endif;
      

      I hope this helps – cheers!
      Marek

  5. Hi Marek,

    Sorry for not getting back to you sooner – got a bit swamped on that project towards the end!

    Thanks for the example code – that’s really helpful. : ) I haven’t implemented a ‘default’ state yet but managed to get a system where the same field group was used for both on-page blocks and reusable blocks, which has given us great flexibility.

    In essence, admins can create a block (say a CTA banner) on a particular page and it’ll be custom to that page. Alternatively, they can also create it within the Reusable Blocks CPT and then add it to multiple pages via a Relationship picker field.

    The trickiest bit for me was figuring out that I needed to nest the checking code – the `if( have_rows(…))` bit – for the relationship field. Definitely an a’ha! moment!

    Thanks again for your help – I’m going to look at introducing this to the site in the next couple of weeks now the site has launched. : )

    Hope you’re all staying safe and well during these bizarre global times.

    1. No worries at all, Rob! I totally understand.

      That’s great to hear that you were able to get this working. Once we started implementing the Reusable Blocks into our websites it was a huge game-changer!

      We’re continually adding new layouts to our Reusable Blocks so that each project becomes more efficient to build while also having more flexibility to implement new functionality.

      Thanks again for your comment and follow up. We love hearing how others in the community are leveraging the platform and tools 🙂

      Take care and stay safe!
      Marek

  6. Hi, really interesting idea. Just wondered why you’re needing to reference the ID of the current post at the top of the codeand then refer to it throughout the have_rows loop?

    As the ACF documentation shows for flexible_content, you don’t need to grab the current post ID, so what extra does that add for this concept?

    1. Thanks for your comment!

      It’s been a few years since this post was written so I unfortunately don’t have a complete answer for you. But if I recall correctly, this was done to ensure that the correct “ID” was being referenced when using the Flexible Content Loop on pages that contained posts (like archive and taxonomy pages). When you’re on archives and taxonomy pages, the ID that is recognized by WP is the first post within the loop. This could cause issues if you’re actually looking to use the flexible content layouts that are defined on that taxonomy/archive and not for the layouts defined on the individual post.

      We may have defined an override for the get_the_ID() function to ensure that the correct ID is pulled in those cases. But unfortunately, I can’t say for certain.

      In the end, I recall that the default ID wasn’t always correct for us, so we added this in to ensure that the correct ID (and layouts) were being used within the various page(s).

      Thanks again!

  7. How can you make ACF flexible content with Oxygen builder, if Oxygen does not use template or theme ?

    1. Hi Martin,

      That’s a great question! We haven’t explored Oxygen quite yet (all of our projects are built using a custom theme that we develop for the client) so I don’t have any information available regarding combining ACF Flexible Content with Oxygen Builder.

      If you figure out a way to get these 2 tools to work together, please comment back with your findings.

      Best of luck!

Comments

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

Request a Quote

If you’re ready to change the trajectory of your business and
excited to partner with the right team to get the job done.

UP

© 2024 NVISION. All Rights Reserved.