Using Advanced Custom Fields without frontend dependency

The performance issues in ACF have been resolved. You can use either ACF or core WordPress functions for accessing the data with the same result performance-wise.

Advanced Custom Fields is one of the most popular methods of creating metaboxes. It provides a simple visual interface for creating the metaboxes, and the resulting metaboxes look great and are easy for clients to edit.

ACF also includes a bunch of functions you can use in your theme to retrieve the values of the meta fields. But I recommend you don’t use these.

For some fields like images, this will introduce additional database queries. See the comment below where switching from ACF functions to the WP core function decreased database queries from 146 to 22!

There’s is no reason to use an ACF function like get_field() when you can use a WordPress core function like get_post_meta(). Using these ACF functions introduces what I’m calling “frontend dependency”.

If you use an ACF field and the ACF plugin is removed or deactivated (because clients…), visitors to the website will see the white screen of death instead of the website. If you go to the bother of wrapping all the ACF functions in function_exists(), the site will load but none of your metadata will be shown.

But the metadata still exists in the WordPress database. If you had used WordPress functions, there would be no changes to the frontend of the website. On the backend the metaboxes would be gone so they couldn’t edit those fields (unless they use the WP built-in Custom Fields metabox), but this is a much less serious issue than a broken website.

I’ve also found that ACF can cause performance issues on the frontend, even if you’re not using their fields. You can add this code to prevent the plugin from loading on the frontend.

This works fine for simple fields, but it might not be clear how the more complex fields are stored in the database. I’ll walk you through some examples.

Repeating Fields

On this page, as many videos can be included as they like (or none at all). I’m using a repeater field which includes a Title, Video URL and Thumbnail (sample fields).

repeating-videos

In your theme, if you look at the meta field for the repeater field ( get_post_meta( get_the_ID(), 'be_attorney_video', true ); ), it will give you the number of items within that field. You can then loop through and access all of them. Here’s how I’m assembling those videos:

<?php
$videos = get_post_meta( get_the_ID(), 'be_attorney_video', true );
if( $videos ) {
for( $i = 0; $i < $videos; $i++ ) {
$title = esc_html( get_post_meta( get_the_ID(), 'be_attorney_video_' . $i . '_title', true ) );
$video = esc_url( get_post_meta( get_the_ID(), 'be_attorney_video_' . $i . '_video', true ) );
$thumbnail = (int) get_post_meta( get_the_ID(), 'be_attorney_video_' . $i . '_thumbnail', true );
// Thumbnail field returns image ID, so grab image. If none provided, use default image
$thumbnail = $thumbnail ? wp_get_attachment_image( $thumbnail, 'be_video' ) : '<img src="' . get_stylesheet_directory_uri() . '/images/default-video.png" />';
// Displayed in two columns, so using column classes
$class = 0 == $i || 0 == $i % 2 ? 'one-half first' : 'one-half';
// Build the video box
echo '<div class="' . $class . '"><a href="' . $video . '">' . $thumbnail . '</a>' . $title . '</div>';
}
}
view raw functions.php hosted with ❤ by GitHub

This snippet also shows you how to use ACF image fields. They store the image ID, so you can get the actual image markup using wp_get_attachment_image( $image_id, 'image_size' );.

Flexible Content

In my last post I described setting up the HTML markup for Full Width Landing Pages. This section is a bit like “Part 2” of that post, since almost every page I set up in that way I’m using ACF’s Flexible Content field to populate the content area.

outboundengine

Flexible Content allows you to create different layouts, which are collections of fields. For instance, a Testimonial layout might have fields for Quote, Person’s Name, and a Photo. A website editor can then create a page by assembling these layouts in whatever order they like, and use them as many times as they like.

Take a look at the OutboundEngine homepage. The header contains a menu and call to action (graphic, text and form). The footer is the dark blue part at the bottom starting with “Sign up and watch your business grow”. Everything in between is the flexible content area. This page is made up of the following layouts: Features, Small Quote, Full Width Content, and Full Width Content.

For this example, my Flexible Content field has a name (meta_key) of ‘be_content’. I have two layouts: full width content (fields: title and content) and content with image (fields: title, content, image). Assuming you set up your page template like I did in my Full Width Landing Pages tutorial, this would be your template-landing.php file:

<?php
/**
* Your Child Theme
*
* Template Name: Landing
*/
/**
* Flexible Content
*
*/
function be_landing_page_content() {
$rows = get_post_meta( get_the_ID(), 'be_content', true );
foreach( (array) $rows as $count => $row ) {
switch( $row ) {
// Full Width Content layout
case 'full_width_content':
$title = esc_html( get_post_meta( get_the_ID(), 'be_content_' . $count . '_title', true ) );
$content = get_post_meta( get_the_ID(), 'be_content_' . $count . '_content', true );
echo '<div class="flex-content full-width-content"><div class="wrap">';
if( $title )
echo '<h2>' . $title . '</h2>';
if( $content )
echo '<div class="section-content">' . apply_filters( 'the_content', $content ) . '</div>';
echo '</div></div>';
break;
// Content with Image layout
case 'content_with_image':
$title = esc_html( get_post_meta( get_the_ID(), 'be_content_' . $count . '_title', true ) );
$content = get_post_meta( get_the_ID(), 'be_content_' . $count . '_content', true );
$image = get_post_meta( get_the_ID(), 'be_content_' . $count . '_image', true );
echo '<div class="flex-content content-with-image"><div class="wrap">';
if( $title )
echo '<h2>' . $title . '</h2>';
if( $image )
echo '<div class="section-image">' . wp_get_attachment_image( $image, 'medium' ) . '</div>';
if( $content )
echo '<div class="section-content">' . apply_filters( 'the_content', $content ) . '</div>';
echo '</div></div>';
break;
}
}
}
add_action( 'be_content_area', 'be_landing_page_content' );
// Remove 'site-inner' from structural wrap
add_theme_support( 'genesis-structural-wraps', array( 'header', 'footer-widgets', 'footer' ) );
// Build the page
get_header();
do_action( 'be_content_area' );
get_footer();

The ‘be_content’ meta field will be an array of layouts used on this page, in order. The fields in each layout will be stored based on that order. So if you have a field named ‘title’ and it’s in the first layout on the page, it will be stored as ‘be_content_0_title’ (counting starts at 0, so the first one is 0).

Options Pages

Metaboxes you add to options pages will store data as options instead of post meta. It will be the meta_key you specify, prefixed with ‘options_’. So, if you had an options page with a Call to Action box (sample fields), you would access those fields like this:

<?php
function be_call_to_action() {
$title = esc_html( get_option( 'options_be_cta_title' ) );
$button_text = esc_html( get_option( 'options_be_cta_button_text' ) );
$button_url = esc_url( get_option( 'options_be_cta_button_url' ) );
if( $title && $button_text && $button_url )
echo '<div class="call-to-action"><div class="wrap"><p>' . $title . '</p><p><a href="' . $button_url . '" class="button">' . $button_text . '</a></p></div></div>';
}
add_action( 'genesis_before_footer', 'be_call_to_action' );
view raw functions.php hosted with ❤ by GitHub

Term Metadata

Let’s say you added a Subtitle field to categories (sample fields). ACF stores these as options as well, with the following format: [taxonomy]_[term_id]_[field name]

So with a field name of ‘be_subtitle’, you’d use the following to access it on a category archive page:

<?php
/**
* Category Subtitle
*
*/
function be_category_subtitle() {
// Make sure this is a category archive
if( ! is_category() )
return;
$category = get_term_by( 'slug', get_query_var( 'category_name' ), 'category' );
$subtitle = esc_html( get_option( 'category_' . $category->term_id . '_be_subtitle' ) );
if( $subtitle )
echo '<p class="subtitle">' . $subtitle . '</p>';
}
add_action( 'genesis_before_loop', 'be_category_subtitle' );
view raw archive.php hosted with ❤ by GitHub

Bill Erickson

Bill Erickson is the co-founder and lead developer at CultivateWP, a WordPress agency focusing on high performance sites for web publishers.

About Me
Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk

Reader Interactions

Comments are closed. Continue the conversation with me on Twitter: @billerickson

Comments

  1. Carles says

    Bill, my last comment was wrong regarding images.

    I’ve checked it out. In my case, one of the images is a subfield. Leaving the parent field (a Repeater) with get_post_meta() but coding the subfield with a “while have_rows()… get_sub_field()…” adds 5 more queries to the database. Those must be:
    1.- Reference
    2.- Value
    3.- Size 1
    4.- Size 2
    5.- Size 3
    which matches the 3 different image sizes that I have declared in my functions.php

  2. Loren Helgeson says

    Thanks for the info. Very valid points.

    I personally had a web project that got so bloated and out of control (one post type had an ACF Repeater field with sub items having their own repeater fields) and it got to the point where the site was unresponsive until I disabled the plugin. Cutting down on the queries sounds like it would fix a lot of that, but I also like the “keep it simple” way of doing things by using core functions over these plugin-specific functions.

  3. Trevor says

    Hey Bill

    Great Article!

    Do you have an example for ACF Gallery fields?

    This is the solution I have come up with so far, just wondering what your approach / thoughts might be??

    $galleryImages = get_post_meta( get_the_ID(), ‘gallery_images’ , false);

    foreach ( $galleryImages as $galleryImage) {
    foreach ( $galleryImage as $photoID) {
    $thePhoto = get_post($photoID);
    $photoURL = wp_get_attachment_url($photoID); // gets photo URL.
    $photoDesc = $thePhoto->post_title; // gets title of photo.
    }
    }

    Any inside would be most appreciated!
    Thank you and again great article

    • Bill Erickson says

      That works, but instead of running get_post() just to get the title, you can simply do get_the_title( $photoID );>/code>.

      Also, I'm not sure in what context you're using this but I tend to use wp_get_attachment_image( $image_id, $size );. Something like this: https://gist.github.com/billerickson/93dca95347be54dbe987317f9522a8cd

      • Trevor says

        Awesome! Your way works much better.

        Thank you for your help, the awesome article and the super fast reply. Really appreciate it . Have a good one.

  4. Kevin H says

    In order to use this method and also allow for shortcodes in an ACF WYSIWYG field you have to do the below:

    $article_content = wpautop ( get_post_meta( get_the_ID(), ‘media_content_repeater_’ . $i . ‘_article_content’, true ) );
    echo apply_filters( 'the_content', $article_content);

      • Kevin H says

        Great information, thank you. Do you keep the ea_the_content filters gist up-to-date? Curious where I could see all filters that can bee applied to the_content, looks like the codex has them in several locations?

        Lastly _unautop you are using, does this just remove the wrapping of a shortcodes content in the tag? Currently I use the below code to do this on the_content… is there a better way to do this when creating your own ea_the_content filter? Thanks as always!


        /**
        * Prevent WordPress WYSIWYG from Wrapping and in tags
        *
        */
        add_filter('the_content', 'filter_ptags_on_images_iframes');
        function filter_ptags_on_images_iframes($content) {

        $content = preg_replace('/\s*()?\s*()\s*()?\s*/iU', '\1\2\3', $content);
        return preg_replace('/\s*(*.)\s*/iU', '\1', $content);

        }

      • Bill Erickson says

        WordPress doesn’t typically add new functions to ‘the_content’, but if they do I’ll try to update that gist.

        I created that by searching the WP core for functions added to the_content.

        Correct, shortcode_unautop() removes paragraph tags from around the shortcodes before running do_shortcode().

        I’m not great at regex so can’t tell you if your filter is a good way to do that (not exactly sure what you’re trying to do there).

        • Kevin H says

          All of this is great Sir, thank you.

          That regex was just to remove the auto wrapping of images and iframes in paragraph tags

          🙂 thanks again

  5. Carles says

    Hi Bill,

    In your Repeater Field example, this part:

    $thumbnail = $thumbnail ? wp_get_attachment_image( $thumbnail, ‘be_video’ ) : ”;

    In my case, I’m showing images that are updated several times a day, so they are stored in a special folder outside WordPress. I have a ‘image_path’ text sub-field with the route for every image.
    The value of that sub-field could be, for example, “/images/ski_resort_1/image_3.jpg”.
    Then, if I have a single field (not a Repeater), I’m able to show the image with:
    $image_path = get_post_meta( get_the_ID(), ‘image_path’, true );
    if ( $image_path ) {
    echo ”;
    }
    Now, I also have a Repeater Field similar to yours, and every post has a different number of images, which I intend to show. But my sub-field is not an attachment, just a text sub-field with the path to each image file. I’ve been looking into the WP Function Reference, but no luck. I’m trying to mimic the elegance of your Repeater Field function, but I’m stuck with this part.
    Could you please give me a hint?

    Thanks in advance

    • Bill Erickson says

      What’s the key of your repeater field and the sub-field? Assuming the repeater field’s key is ‘be_images’ and the sub-field is ‘url’, you could display all images like this: https://gist.github.com/billerickson/e2e2f8a4bdf38e8e1efd71e319378c54

      The code looks a bit more complicated since I make the keys variables you can change at the top.

      I’m using home_url() to convert it to a full URL based on the site’s URL (see https://codex.wordpress.org/Function_Reference/home_url ).

      • Carles says

        Bill,

        I was about to answer my own question. 🙂 It was as easy as deleting:
        $thumbnail = $thumbnail ? wp_get_attachment_image( $thumbnail, ‘be_video’ ) : ”;

        My complete solution is then:
        if ( $images ) {
        for( $i = 0; $i < $images; $i++ ) {
        $image_name = get_post_meta( get_the_ID(), 'images_' . $i . '_image_name', true );
        $image_path = get_post_meta( get_the_ID(), 'images_' . $i . '_image_path', true );

        $class = 0 == $i || 0 == $i % 4 ? 'one-fourth first' : 'one-fourth';

        echo '’ . $image_name . ”;
        }
        }
        Maybe your code is better or more efficient?
        Regarding sanitization, given that my field is a text sub-field, is it right to do esc_html instead of esc_url?
        Thanks for the quick answer!

  6. Carles says

    Bill,

    there seens to be a problem with the Repeater code. When doing:
    for( $i = 0; $i < $image; $i++ ) {
    some code;
    }
    I've checked the column-classes and everything seems in place, but with 5 or more images, they all go in the same line, with the 5th as 'one-fourth first' again. But when doing:
    for( $i = 0; $i < 5; $i++ ) {
    some code;
    }
    then it only shows the expected 4.
    Is there a way to make the Repeater automatically build a second, third, etc. lines each one with, say, 4 images or any other 4 items?

    Thanks

    • Bill Erickson says

      It sounds like the repeater code is doing exactly what it should. The fifth item is getting the class ‘one-fourth first’ as it should the the first item on the second row. If it’s not appearing on a second row, are you sure you have Column Classes in your theme?

      • Carles says

        Forget it Bill, it was the Flexbox code. Once I deleted it, all is fine.
        I have to research on making column-classes live well with it.

        Thanks very much.

  7. Glenkg says

    Hi Bill,

    I am sorry for this stupid question for I am not on the same level as many other people here. I like the code for the flexible content instead of the get_field and get_subfield for the reasons you were telling about. But are these the steps I have to do:

    1) I put that template_landing.php in the root of the template.
    2) make a group be_content, choose flexibel content as a layout. name field group label+ name: “full_width_content” and name subfield label+name: ” title”, the second “one _content”, and then do the same for the group “content_with_image”
    3) then use the template “template_landing.php in the page you want

    It should appear on the page then right? Because I don’t see anything yet. Or does it need to be hooked still?

    I am just trying to figure out how I can get a flexible content field into my event pages of my genesis theme under the “normal”content. But I am struggling with it. It seems that I am missing something..

    • Bill Erickson says

      I think you’re over-complicating it. Build out your Flexible Content metabox with whatever labels and names you want. Then the template file you use will depend upon the content type you’re using.

      Are “event pages” actual Pages in WordPress (managed in the Pages backend section), or are they posts in the “Events” custom post type created by you or a plugin? Use the Template Hierarchy chart to help figure out what template to create to target that content.

      Once you’re in the right template file, you can add the following code to see all the metadata on the post: print_r( get_post_meta( get_the_ID() ) );

      Looking at all the metadata along with your ACF metabox will help you understand how the data is stored. It might be clearer than my post above. There’s really two key things:
      1. The meta value for the flexible content field itself simply stores a listing of what sections are used and in what order. For instance: https://gist.github.com/billerickson/6ae94fd217c3074d0c1335a85fe83848
      2. All of the subfields use the following meta key structure: [flexible content name]_[order number]_[sub field name]. So a sub-field of ‘content’ in the first section (count starts at 0) and with a flexible content name of `be_content` would have a key: `be_content_0_content`.

  8. glenkg says

    Hi Bill,

    Thank you for your answer. I think I do make it more complicated, sorry. I said Event-pages, I should have said “event-pages.php” instead of “template_landing.php”.

    I have several actual pages that should have the same layout through flexible content (in the wordpress backend section “pages”). In each page i will choose (in the option attributes within a page) as template “event-pages.php”.

    So what i am struggling with is the code I have to put into the “event-pages.php” to show the layout metaboxes on all the pages I activated the template “event-pages.php.

    And thank you for explaining the order. Good to learn that too 🙂

    • glenkg says

      disregard the last question Bill, I think I am starting the understand it a bit. I now know what you mean with explanation of print_r( get_post_meta( get_the_ID() ) );

      I am novice with this, trying to figure it out.

    • Bill Erickson says

      I don’t see anything wrong with that code. The only reasons I can think for it not running are:

      1. You are not using an HTML5 child theme, but are using the older XHTML version (in which case change genesis_after_entry_content to genesis_after_post_content)
      2. You have a custom query running very early (prior to template loading, so must be in functions.php) and are not properly resetting $post using wp_reset_postdata();.
      3. There is a fatal error earlier on the page that is preventing this code from running
      4. Your page is not using the “event pages” template
      5. Your Genesis child theme is not active

      • glenkg says

        Bill, thank you for helping me out. I really appreciate it, this stuffs makes me feel like a dumbass sometimes, sorry to bother you with these questions. But you really explained it in a way I can start to better understand it. through your answer I found the problem. 🙂 Now I am working on getting an full width hero column, a two row image – text column, a two row text – image row, fingers crossed…

  9. Carles says

    Hi Bill, one final question:

    I’m able to show rows of 4 pictures and your code is working fine. But I’m trying to wrap every row of 4 photos in a , so the fourth item (4th photo) should end with an added and then the 5th should open another .

    Is there an easy way to accomplish this?

    Thanks very much

    • Carles says

      I see my comment didn’t come complete, I’ll repeat the question:

      Hi Bill, one final question:

      I’m able to show rows of 4 pictures and your code is working fine. But I’m trying to wrap every row of 4 photos in a div class, so the fourth item (4th photo) should end with a closing div added and then the 5th should open another div class .

      Is there an easy way to accomplish this?

      Thanks very much