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).


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:

$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.


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:

* 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>';
// 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>';
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
do_action( 'be_content_area' );
view raw template-landing.php hosted with ❤ by GitHub

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:

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:

* Category Subtitle
function be_category_subtitle() {
// Make sure this is a category archive
if( ! is_category() )
$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 a freelance WordPress developer and a contributing developer to the Genesis framework. For the past 14 years he has worked with attorneys, publishers, corporations, and non-profits, building custom websites tailored to their needs and goals.

Ready to upgrade your website?

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

Let's Talk

Reader Interactions


  1. Tony Eppright says

    Thanks for this, Bill! I was using ACF a lot before their update, and wondered if I would continue using them because of the major changes.

    I also wondered if this was possible, but never tried it. This makes me feel a lot more comfortable!

    I wonder why ACF recommends using their functions instead of the exsiting WordPress functions? Anyways, thanks so much for taking the time to share this!


  2. Ren says

    Awesome stuff, Bill. I love the ACF plugin and its API but you make a great point in favor of using get_post_meta().

    I’ve been working on a new project that I wanted to incorporate some meta boxes and simple custom fields into and I was planning to use ACF but your CMB library is phenomenal and works great.

    Thanks for the awesome post.

  3. Grégoire Noyelle says

    Thanks Bil
    And do you use transient to save the value when you have to deal with complexe page with a lot of meta (flexible, repeater)?

    • Bill Erickson says

      No, transients are best left for complex queries that you’re caching or external APIs that you’re calling, like latest tweets.

      It would be great if ACF allowed you to store meta as a serialized array though. If you have a repeater field that has 5 sub-fields and you have 4 items in it, that’s 21 meta fields you’ve created. If you do the same repeater in CMB it’s a single meta field since CMB stores repeaters as serialized array.

      • Joshua says

        It seems like most of this can be reproduced with CMB, just without the UI that ACF provides. Given that the front-end display of the information is all custom development, though, I would think the UI isn’t the reason to use ACF over CMB? I’m hesitant to utilize a plugin like ACF for metaboxes, for the same reason you so elegantly described: because clients… Meanwhile I can place CMB in a custom functionality plugin and feel a little better about reducing potential conflicts and user error on plugin updates.

        Anyway, just wondering if there is any strong reason to use ACF over CMB in general, or just specific instances where you’ve found it’s better?

        • Bill Erickson says

          I try to use CMB most of the time. There’s only two instances I’ve found where ACF is the better choice (since CMB doesn’t do it and would require a lot of custom coding to add support):

          1. Repeating fields that include a WYSIWYG. TinyMCE can’t move in the DOM, which is why CMB doesn’t support it in repeating fields. The previous version of ACF used a non-TinyMCE wysiwyg which allowed it to move. I think the latest version of ACF uses TinyMCE but I don’t know how they’ve overcome the DOM issue.
          2. Flexible content layout. Clients really like this. The ability to assemble pages with different rows of content, whichever content types they like in whichever order they like.

          But my guess is the majority of people using ACF aren’t using it for these reasons, but rather for the nice backend UI for creating metaboxes. I prefer CMB, a purely code-based metabox creation, but a lot of others seem to prefer a GUI.

          I probably can’t convince them to use CMB, but I can point them in the right direction when using this plugin.

        • Joshua says

          Awesome, this is just as I thought. I love CMB and hope to have some time to debug and help out on the version 2 in the next few months (as you mentioned below, “patches are the best form of donation”).

          I have a follow-up question related to your Event Calendar – it seems to me that is perfect for CMB, but I noticed that Jared Atchison contributed changes to make the metaboxes stand-alone on that one. Was this just to make it less dependent on a another project, or in anticipation for CMB2? It seems like CMB would make it more easily expandable as a template for event calendars.

        • Bill Erickson says

          We stripped CMB out of this plugin to make the plugin itself much leaner and to allow developers to use their tool of choice to customize it. There’s a filter in there (always has been) to disable the plugin’s metabox so that you could build your own in a core functionality plugin or by using a tool like ACF.

          I didn’t want to lock people into using CMB to customize the metabox. When I build with it I’ll turn off the built-in metabox and use either CMB or ACF to build a metabox with all the features I need.

      • Phil says

        Actually, there is a way (sort of): ACF Table Fields ( stores its data in a serialized format, if I’m not mistaken.
        It would be really nice to have the option for ACF to do the same, so we could use ACFs nice backend UI – it’s just much, much more elegant and customer-friendly than CMB2, IMHO, unless I’ve overlooked something somewhere.
        This is a great and very useful post, even in the contest of your great and very, very use- and helpful site.

        • Bill Erickson says

          I’ve actually stopped using ACF. I’m tired of their non-backwards-compatible changes. I switched to Carbon Fields, which is the perfect balance between developer friendly (like CMB2) and a nice looking backend UI (like ACF).

          • Phil says

            Oh, interesting, thanks for the link!
            Out of curiosity: do you use the carbon_get_post_meta functions in the frontend, or do you again work around the plugin, so to speak, to gain more independence and/or performance?

          • Bill Erickson says

            I built my own function that uses carbon_get_post_meta, for a few reasons:

            1. I’m tired of learning the data storage structures for these different plugins. I wanted to create a single function that I can change out depending on the library I use.
            2. Carbon Fields can be used for post meta, term meta, user meta, theme settings, and more. There’s a bunch of carbon_* functions, and I didn’t want to remember them all.
            3. I already had a custom field function (ea_cf()) which provided a shortcut for simple fields (ex: ea_cf( ‘field_name’ ) ) and formatting options for more complex ones (prepend/append text, escape output…). I wanted to leverage these features on all the field types.
            4. By using my own function, I can add a fallback to get_post_meta() in case carbon fields is not active.

  4. Jon Brown says

    Thanks for this! I’ve long considered get_field() one of the stupidest function in existence… ok that’s a bit extreme.

    This post however is brilliant and I’ll refer to it often for handling all the less common and more complex cases.

  5. Victor Marsala says

    I just wanted to say two things: If anyone’s on their developing journey like I was, and has yet to jump onto the custom metabox train, DO it. In my opinion, CMB is the best thing to happen to WordPress since Gravity Forms and Genesis itself (ignore chronology =)

    I even was one of those people who thought “Well, why not just use Custom Fields?” Like many things, it makes sense to me, and I would do it, because I understand what I’m doing. I’ve done as much on my own site. But plenty of good and intelligent people could give two hoots about entering aa variable name or even picking it out pre-made from a dropdown. Plus the variety you get in field types from CMB is both practical and professional.

    This is a LIFE saver. I’ve been an on-the-books company, a D/B/A freelancer, for over a year and a half, no slouch to coding, even, and I’m just getting to this now. But this library is not only drop-dead simple, but makes you look like a genius and a bad-arse with hardly any up front work. You’re pruning and replacing, and then burping out or iterating through loops.

    Recently I had a bed and breakfast site to make, and
    I’m a nerd for formatting and styling information. In the sidebar for room rates, as a result, there were headers and EM tags and spans and all sorts of different font weights and colors. This client -could- get the concept of copying and pasting, and only changing the things that, well, change, like the rates themselves. But why leave it up to chance? Enter CMB to be my madlib system. I throw the fields for the rates, well-labeled, up on the side of the edit screen under the main Update box, and it comes out perfectly formatted every time on the same spot on the resulting page.

    It really comes across like a custom-built WordPress just for them and their purposes. Value added without nearly as much work as it looks like it is. Allows you to charge more as well, for the time-saving and motivation-bolstering nature of what you’re giving them.

    What really makes it magic for me, though, are the repeating fields. No need to go all the way into custom post types for minor pieces of information. If they’re just burning through things in an established pattern, like testimonials, you just give it a special page and let them pop ’em out. So clean and organized.

    Ugh. Total nerd mode. God bless. Is there some place to donate for this?

      • Jon Brown says

        Interesting… I hadn’t heard about CMB2 and am having trouble finding a “reason/goal” for the rewrite.

        Any idea if it’s prepping for the core metadata UI API project?

        Have you used CMB2 in production? Seems pretty complete already.

        • Bill Erickson says

          I’m not sure why, but my guess is so that they can make some non-backwards compatible changes to it.

          I haven’t used it yet, or really looked at the code much. I just got an email a few days ago that I was added to a repo 🙂

  6. TMF says

    But how do you use get_post_meta with a shortcode?
    With get_field I can still run the code (specifically – display-posts-shortcode.


  7. Robin says

    Thanks for this post, Bill! Took some patience but I removed all the get_field calls in a current project, including relationships, which took a bit more pondering. Appreciate your sharing your expertise!

  8. Rachel Vane says

    Thanks Bill –

    Such an awesome post – it totally improves the way I use the repeater fields. This past year, as I’ve been learning to develop with Genesis and I have found myself on your site a lot. 🙂 I’ll have to dig more into CMB.

    So, my question (bear with me, because it may be silly) is that when I use ACF with Genesis I use genesis_get_custom_field((‘field_name’) – would you discourage that and is it your opinion I should switch to get_post_meta() ?


    • Bill Erickson says

      You won’t have any dependency issues using the Genesis function for post meta, since your Genesis child theme will only run if Genesis is active anyway. But I tend to prefer using get_post_meta() anyway since it makes the code easier to read for non-Genesis developers.

      • Anne Katzeff says

        I’m definitely going to try out CMB, it looks great Bill.

        I’m in the same situation as Rachel: using ACF with Genesis child themes. So, what happens if we de-activate ACF with Genesis themes? Will that bring the white screen of death?

        • Bill Erickson says

          If you use the ACF functions (like get_field() ) in your theme, you will get a white screen if ACF is not installed. The two ways around this are:

          1. Don’t use ACF functions, use WP core functions ( get_post_meta() )
          2. Every time you use an ACF function, wrap it in function_exists(). Ex: if( function_exists( ‘get_field’ ) get_field() … )

          • Anne Katzeff says

            Hi Bill,
            Thanks for your reply. I’ve been using ACF as you describe, without ACF functions and wrapping functions in a if {function_exists} ( ‘get_field’), etc., The snippets were found within the Genesis community. So, I’m good, phew!

            I just took another peek at the ACF resources page and see the issue you are calling out for us to pay attention to. Makes sense.

            Thanks again for all you do in the Genesis community!

  9. John says

    Hi Bill,

    I’ve been using your technique here for a little while now, so thank you for that. I’ve never had the need to out put repeated fields of repeated fields and a possible repeater field deeper but I have a project just like that. I however cannot get that to work using the method you’ve shared here.

    My approach was to write another for loop inside of the current one and use the same method output the repeater field inside the repeater field.

    Am I in the right direction?

  10. Andrei Barbuta says

    Bill, since CMB2 just came out, what is your current opinion on CMB2 and ACF Pro since both of them had major updates recently (ACF v5).

    Why is it one better then the other and when is which situations to use each.

    Thank you.

    • Bill Erickson says

      Personally I only use ACF for very complex metaboxes – repeating WYSIWYGs, flexible layouts, and tabs. And over time I expect CMB2 to get these features so I’ll use ACF even less.

      First, CMB2 is more developer focused. It doesn’t have a backend UI for building metaboxes. Instead, you simply modify an array with the fields you’d like. See the example-functions.php file for examples. This simplifies the plugin itself, and minimizes the chances that the client will mess with the metaboxes once launched. I know you can hide the ACF menu item using filters (I do when I use it), but I prefer not using a tool that has large features that I then hide.

      Since it is code-based, it’s easier to place the metabox code in a relevant location, like in your theme or plugin (as opposed to being in ACF’s plugin settings). So when I build an events plugin, I include the code registering its metabox in the plugin itself. While technically ACF gives you the ability to export fields, CMB2 is much easier to use in it’s code-based form than ACF is. ACF is really designed as a visual metabox builder with a code-based form tacked on the end.

      CMB2 can be packaged in your theme or plugin. You can have multiple plugins and themes all including CMB2 and it will load the latest. So if you are working on a plugin that you plan to publicly distribute, you can safely package the CMB2 code in there so it works out of the box, as opposed to telling people to “install ACF first” or packaging ACF Lite which doesn’t update (to my knowledge). You can also install the CMB2 plugin to make sure you’re always running the most up-to-date CMB2 code, since once a plugin is built they might not release updates every time a new version of CMB2 is released.

      Finally, if you plan to create metaboxes for option pages or term meta, ACF will cause a huge performance hit which CMB2 won’t. For every field or sub-field you have in ACF, it creates 2 meta fields in the database. One is the actual field (ex: ‘my_field’ ) and one is a hidden field that ACF uses internally (ex: ‘_my_field’ ). It doesn’t serialize this for option pages or term meta. So if you have a repeating text field on an options page and create 5 items in it, you’ll have 12 options stored in the DB for this (2 for the main field, 2 for each of the 5 subfields). If you use it on term meta, you’ll have 12 times however many terms you use it on. You can quickly see how this will flood your database with options.

      CMB2 will store options and term meta (which are really the same thing, both in the options table) as a single, serialized array of field values. So your option page would be stored as 1 option. You would have one option for each of your terms for which you used term meta. And if you plan to have lots of term meta (or lots of terms, or both), you can use wp_large_options to store this metadata (summary: much better for performance). The options table loads on every page (options are supposed to be site-wide settings). wp_large_options creates a hidden post type and stores the term meta as post meta. This means it isn’t loaded on the page unless you actually need it. So if someone lands on your term meta page and you call your metadata, wp_large_options converts that to a get_post_meta() pulling it from the hidden post field.

      • Andrei Barbuta says

        Thank you so much. Your response cleared a lot of stuff in my mind. I will start to get to learn CMB2 while still using ACF for current projects and move to CMB in the future. I want to get into selling themes and the performance comparisons made it clear which is the right path to take.

        One more thing that I need to clarify, do you use both ACF and CMB2 on a project? For example using ACF where you need Flexible and Repeateable WYSIWYG fields and CMB2 for other stuff or you just use one? What if the client asks in the future for some improvements and he needs Flexible or Repeatable WYSIWYG and you used CMB2 for that project?

        P.S. Sorry for the late reply. Lots, of changes in my life, starting to go into business on my own. Bureaucracy hit me like a brick! 🙂

        • Jon Brown says

          You can use ACF and CMB at the same time. Generally I’d avoid doing so, but in cases like that I wouldn’t hesitate to.

        • Bill Erickson says

          Yes, I’ve actually done this on a project recently. I used CMB for post meta, options page, term meta, and user meta. I then used ACF for a single landing page (needed the flexible layout field).

          If you’re doing something complex with ACF and then need one more simple metabox, I’d probably just keep it all in ACF. But when it comes to areas like user meta, term meta and site options, I’d rather use both CMB and ACF for their strengths rather than do it with a single plugin.

  11. Rachel says

    I’ve been loving ACF lately and I hadn’t realized you could use post meta! I mean, it makes sense because that’s all it is – post meta that ACF arranges nicely for you on the dash, but I can’t believe this never occurred to me.

    Thanks for the enlightenment. Much appreciated. I’ll def give this a try soon. 🙂

  12. C R says

    Thanks so much for your blog and article. I find them so enlightening and helpful as I work my way through WordPress and Genesis. And I admire your willingness to take time to help others! (Your responses to comments are awesome!)
    In general- for a home page with different sections- would you normally make a widgeted home page or would you use ACF or CMF? I’m just trying to understand when to use what..

    • Bill Erickson says

      I would definitely build it with metaboxes on the homepage (either ACF or CMB).

      In my opinion, widget areas should only be used for sitewide (or sectionwide) content. A sidebar is the same across the whole site, so it makes sense to manage it in a specific area. But page specific content belongs on the Edit Page screen.

      I hate seeing themes that have 20 or 30 widget areas to control the content of different pages. That’s a poor user experience.

        • Bill Erickson says

          I think they’re popular with commercial themes since it leverages built-in WP functionality. It’s simpler to build and less likely to conflict with something else.

          That’s one of the reasons I like building individual sites for clients where I can set up everything for them, as opposed to selling a theme and hoping they can set it up themselves. I can do it the *right* way and make sure all dependencies are taken care of.

  13. Sushant says

    Hey Bill,

    Nice one, I have been playing around with the ACF and repeater addon and was able to implement things as I wanted to. I now am working this one project, where I need to implement a custom fields on front end that too with repeating attribute and once they are submitted they should also reflect with in back end. Its like with each of the posts, I need to provide ACF on front end and when admin checks it should come with exact same fields filled with the information which was submitted from front end. And one more important thing, these fields will appear for specific members only (those do not have access to back end).

    I was able to call out the fields with user login validation but not able to implement repeater out there. I keep getting errors, would it be possible for you to look in to the code? I can share in next comment.

    Thanks in advance.

    • Bill Erickson says

      I’ve never attempted to use ACF on the frontend like that. I always use Gravity Forms for frontend submissions.

  14. Sarah says

    Thanks for the post! I’m considering moving to using CMB2 from ACF.

    Had a quick look at the docs and it seems that the CMB2 library handles sanitizing/escaping of data – am I right? Presumably though there would be cases where you’d still escape on output – depending on context? Would be useful to know how you handle this…


    • Bill Erickson says

      Yes, when adding a field you can specify the type of sanitation you’d like done. For instance, when registering a text field that will hold a url, you could add ‘sanitization_cb’ => ‘esc_url’. Or you could pass your own function to write a custom sanitization function for that specific field.

      It will be up to you to escape it on output. $url = esc_url( get_post_meta( get_the_ID(), 'be_website_url', true ) );

      • Jim Camomile says

        I am very confused or maybe just missing the point. I am not sure why you would esc_url here since this is input rather than output. Wouldn’t be better to use a kses filter (sanitize on input, escape on output )?

        • Bill Erickson says

          Because if you have a URL field and you just run it through kses, a non-url-formatted value can be saved. Using esc_url() on input ensures only a URL gets saved.

  15. Kevin says

    I have figured out how to take away the dependency for much of my uses of the ACF plugin, thanks a ton!…

    I am having trouble with the Gallery field. Simply I would ask how to get and echo the url/src, alt, and caption for each image as it loops through the “for each” listed on the doc page of the gallery field:

    My gist here shows what I have, if you could kindly take a gander and guide me in the right direction. Thanks so much as always.

    Note: the code on the gist is not working, it just shows what I was trying to get working 😉

    I am thinking I am missing how to parse each piece from the ACF Gallery image array, such as the url, caption and alt…

  16. BenM says

    Excellent suggestion, but how do you replace get_field_object() to retrieve all the values for a custom field ?
    Values are stored in a post (so in wp_posts). If you don’t use this post, you probably need a sql query.

    • Bill Erickson says

      I’ve never needed to access the field metadata before. Can you provide a use case?

      I guess you could use it to pull in the available choices of a select field, but I still don’t see how that would be necessary in the theme. I just escape the output.

      • Jon Brown says

        The use case is exactly that. Often I need to output a field label, not the field value.
        $field = get_field_object(‘jobtitle’);
        $value = $field[‘value’];
        $label = $field[‘choices’][ $value ];

        //$value might be “vp”
        //$label might be “Vice Presiedent”

        ACF’s functions do a lot of error checking and “formatting” of what gets returned by get_field_object() depending on the type of field… So this remains one thing I still use ACF functions for, even though I occasionally look for an alternative like I was today.

  17. Matt Whiteley says

    Hey Bill,

    Great article.

    What are your thoughts on simply including ACF in the theme itself ( and continuing to use the ACF get_field functions and the like?

    This would remove the dependency issue as the plugin would be directly integrated in to the theme so the user wouldn’t be able to disable it. This has been my workflow recently with ACF for this specific reason. It also removes the need to wrap each function in the ‘function_exists’.

    Love the blog and all that you do for the WP Community.



    • Bill Erickson says

      That’s a good approach and I’ve done that on a few projects. My main concern with it though is ACF wouldn’t get updated, so unless you are keeping up with it you could end up with unpatched bugs.

      • Matt Whiteley says

        Ahh…excellent point. I suppose get_post_meta is the only fail-safe way to do it and I’ll probably make the switch back. Thanks again!

  18. Henry Reith says

    Hi Bill,

    This is a fantastic post thank you. I have referenced it a lot over the last few months while working on new projects to make sure my sites won’t fall over if ACF is uninstalled.

    However, I have struggled with one thing, if I place the PHP export from ACF inside a mu-plugin it doesn’t show up. I guess this doesn’t because the ACF functionality hasn’t been activated before the mu-plugin. Is this something you have come up against before?

    • Bill Erickson says

      My guess is you can place it in a mu-plugin, but you’ll need to wrap it in a function that’s hooked to ‘init’ or ‘plugins_loaded’ so that your function runs after ACF is set up.

  19. Walter says

    Is there a way to sanitize the output of the wysiwyg editor in CMB2? In this case it contains an image added from the media library. It seems that you could separate them into separate fields using a file upload field and esc_url() and then a text field, but that negates the convenience of adding media through the wysiwyg editor for the end user.

  20. hans says

    first, thanks for the article, great info!
    BUT what is bad in just including the ACF plugin in my theme so the customer doesn’t need to even install it?
    plus if i export the fields in PHP and put them in my functions.php (or use json) i should have a rock solid solution(?).
    i took a look at CMB and … i think ACF gives me WAY more optionsand it just looks sweet. i build my gallerys with the gallery field, have flexible fields with tons of options, repeater fields … i do not need drupal 😉

    • Bill Erickson says

      As far as I know, if you include ACF in your theme it won’t receive any updates. Given the size and complexity of ACF, it seems to get updated all the time to fix newly found issues.

    • Matt Whiteley says

      Hey Hans,

      I had the same question that Bill addressed. The main downfall to doing it this way is that it no longer gets automatic updates if it is included in the theme, so unless you are managing the site and handle the updates you can end up with un-patched bugs.

      I do love ACF though and typically just tell clients not to touch it (works most of the time…). I now use ACF is 100% of my client projects.



      • Bill Erickson says

        Yes, I recommend you just use the plugin and tell the client not to disable it.

        This post isn’t anti-ACF, I still use it on many projects as well. It just points out that there’s no real need to use ACF-specific functions in your theme when you can use the WordPress equivalent.

        • Matt Whiteley says

          Absolutely – I should note that I did take your advice and no longer use the get_field, the_field, the_sub_field, etc…I use the built-in WordPress functions (ie. get_post_meta) per your suggestion.

          Definitely wasn’t saying this post was anti-ACF! I’m there with you.

        • hans says

          thanks a lot bill this is a very good point.
          so i think my steps have to be:
          buying a “personal licence” of $25 for each customer who is buying my premium theme and sending the key to the customer
          – installing and activating the ACF plugin automatically when the customer activates my premium theme (i do this with “tgmpa”)
          – the customer has to enter the licence key to enable automatic update function on ACF

          this is a little drawback because when i want to sell each premium theme for e. g. $50 i have to give 50% away … hmmm.

          • Bill Erickson says

            Why not by the professional license once and use that key on all sites? That’s what I use for clients. If they need support they can either go to you or purchase their own personal license.

  21. hans says

    hi bill,
    thanks for your quick answer!
    i just have not seen this part of the ACF 5 pro licence that says:
    – “Updates for Unlimited Sites”
    my fault 😉

  22. Queequeg says

    Hi! Thank you for your post, it is very helpful to me. I work on a site that uses ACF and I needed to delete some of these fields, but I still need their values. Now I’m facing some problems, because of the use of get_field().. I’ ve got a repeater field with three subfields, one of them a taxonomy select field. I would like to retrieve values from a subfield “name”, where the equivalent taxonomy subfield “type” has a specific selection. Before deleting the fields I had something like this:
    $items = get_field(“items”, $id);
    foreach($items as $t) {
    if ($t[‘type’] == selection5) {
    return $t[‘name’];}
    If I use get_post_meta() I only get the number of items, or nothing. Is there a correct way to replace the above function? Thanks in advance!

    • Bill Erickson says

      In your theme file place this: print_r( get_post_meta( get_the_ID() ) );

      That will show you all the meta attached to the post. Go through it, find what you’re looking for, and you’ll see the proper key to use.

  23. reader says

    could you make the text harder to read here? light gray text on this bg? wow not cool..
    Why not try say p= 3c3c3c at least? Im not even that old but god forbid if I were like 50 or something..
    Im 19 and I am not color blind or anything but still??? is it me? No its you..
    Plus try a larger font size say even 16px I mean this is terrible.. your text on your bg on this site i mean other than that you are a very nice man with much knowledge..

  24. Dylan says

    Thanks for writing this up Bill. Your approach works great, the only downside I can see at the moment is that changes to ACF fields don’t display in the post preview unless you make changes to a native wp field as well. Fields preview correctly when using the ACF functions. The plugin uses the get_post_id function to return the preview post ID if the post is being previewed. Would you just implement a simplified version of this in your theme?

    • Bill Erickson says

      Yes, if you wanted the updated metadata to appear on previews, write a helper function that uses the exact same logic you linked to.

  25. Dmitriy says

    Thank you so much for this. Wish I saw it before spending hours troubleshooting. I have WP GeoDirectory alongside ACF and they don’t work well together – all GeoDir pages fail to retrieve any ACF values. Falling back on the native WP functions did the trick! Cheers!

  26. Patrick Fortino says

    Bill, Thanks for this very clear tutorial. It helped me get a much better understanding of how custom fields work.

    There is an error in your code for flexible content:
    // Content with Image layout
    case ‘content_with_image’;

    The case should end with a : not a ;
    I was getting this error: Warning: Invalid argument supplied for foreach() in /…

    In order to fix the foreach invalid argument error, I had to change the code to this:
    foreach ((array) $rows as $count => $row ) {

    I assume the code works for you, so not sure why I’m getting the error, but the code above fixes the error message.

    • Bill Erickson says

      Thanks! I’ve updated the code. That’s what happens when you write the code in a Gist without testing it 🙂

      • Patrick Fortino says

        FYI for anyone else who is getting this error with the Flexible Content code:
        Warning: Invalid argument supplied for foreach() in /…

        After further testing, I found that it was my php version that was causing the error. Changing to php 5.6.10 or higher fixed the error.

        • Sarah Hills says

          I also had problems with the foreach loop for the Flexible Content code.

          As an alternative, I used: “for( $count = 0; $count < $rows; $count++ )“

  27. Uri says

    Hi BIll.
    Experimenting with this very useful ‘best-practice’ of not having a front-end dependency and struggling with one of my fields in ACF . It’s an image type field and when I try to echo it out directly to a style tag I am getting an id instead of the image URL?

    Example Code:
    $program_background_image = get_post_meta( get_the_ID(), ‘cii_programs_’ . $count . ‘_program_background_image’, true );

    and then trying to use it like this:
    echo ”;

    Any ideas how to pull image fields using get_post_meta

    • Bill Erickson says

      Advanced Custom Fields stores the Image ID, which much more useful since you can use it to generate an image of any size. Use the wp_get_attachment_image( $image_id, $size ) function. If you wanted to output the ‘full’ image you uploaded, do this:

      $image_id = get_post_meta( get_the_ID(), 'cii_programs_' . $count . '_program_background_image', true );
      echo wp_get_attachment_image( $image_id, 'full' );

      If there is a specific image size you want use, specify that instead of ‘full’.

  28. Luyen Dao says

    It seems like, more than any other plugin once you go with ACF, you really have to go all in.

    Another way to approach this, if you have full control over the theme development is to make sure that the plugin cannot be removed (easily), beyond what you’ve stated in your article are there any performance gains from using the native functions to get meta data?


    • Bill Erickson says

      I haven’t done any performance testing, but I do know the ACF functions return a lot more information so they’re probably doing more than one query. For instance, if you `get_field()` for an image field, you end up with a large array of data.

      But if you `get_post_meta()` that field, you get the image ID. That tells me that ACF is using `get_post_meta()` to get the ID, then running additional functions like `wp_get_attachment_image_src()` for each image size to get all the additional information.

      Again, I haven’t done performance testing to see the impact of this, but I like using the core functions and then specifically requesting the information I need rather than getting a dump of all possible information related to that meta field.

      If you do use `get_field()`, the third parameter is $format. If you set that to `false` you should get the same output as `get_post_meta()`

  29. Carles says

    I’m astounded. Seriously.

    I’m developing a Ski site with Genesis that makes heavy use of ACF’s features. There are several Repeaters and Flexible Contents for every single page. According to the Query Monitor plugin, that adds up to 18MB of memory use and 146 database queries. As a skier, I know you’ll be interested :D, beta in
    I’m not a pro (so bear with me) and haven’t found any other article like this -and believe me, I’ve tried-, but found a lot of people defending the use of ACF’s own code over WP’s native one. So I guessed very few people (only the true pro’s) were coding like this and thought “well, this won’t be a big deal, just a very technical one”. But the huge number of queries was a serious concern. So I was eager to give it a try.
    Since I need the Flexible Content feature, I prepared a combined ACF/CMB2 solution. As I had all the ACF’s fields already made, I began by just changing the “get_field”, “get_sub_field” and “while ( have_rows() )…” code with “get_post_meta()”. Still no CMB2, no ACF update, no nothing.
    To my surprise, the memory usage has fallen from 18MB to 14.23MB and database queries from 146 to… 22! Truly shocking.
    My understanding was that your article was all about good practices and avoiding problems with clients. In the comments, you even mention that “CMB stores repeaters as serialized array”, so I thought if one wants to reduce database calls, that’s the way to follow.
    So Bill, I have two questions: do I still need CMB2? And more important, what did just happen with my queries? What’s the magic?

    Thanks for your really useful articles, they are very enlightning.

    PS.- Regarding sanitization, I’ve digged into the Codex but that wasn’t clear to me. In your code, you use “esc_html”, “int”, etc. and I’ve seen others like “esc_js” or “sanitize_text_field”. I’m not sure which one to use for every type of ACF field. Do you know of any article or post with detailed info? Thanks again.

    • Bill Erickson says

      Thanks for the performance details! I’ve updated the post above to make note of it.

      When the post first loads, an additional query runs to retrieve all of its metadata. So adding additional meta fields will not increase the number of queries (that single query still gets all of it) but will increase the memory footprint based on the size of that data.

      But for some fields, ACF runs additional queries for related information. For instance, the image field stores the image ID as post meta. But if you use get_field(), it returns a bunch of additional information including the image object and the URLs for the images at different image sizes.

      If you must use ACF’s get_field(), you should set the third parameter to ‘false’. As you can see here, get_field() accepts three parameters:
      – Field Name (required)
      – Post ID (optional, defaults to current post if not specified)
      – Format Value (optional, defaults to true)

      So if you do get_field( 'field_name', get_the_ID(), true ); you’ll get just the image ID, the same as if you used get_post_meta( get_the_ID(), 'field_name', true );

      Daniel Bachhuber has a great resource describing how to sanitize input and escape output

      • Carles says

        Nope, no more get_field() anywhere. It’s all get_post_meta(), even for the Flexible Content, based on your code and some tweaking. I must say I struggled with those a bit, as I thought the switch() you use in your code was just for your specific needs. But when I tried that, it went really smooth.

        Let me clarify something, though: in order to get a good scheme in my php code, I first declare the variables for the field (e.g. $snow_depth = get_post_meta( get_the_ID(), ‘snow_depth’, true ); Then, later in the “Snow Depth” section of the code, I take charge for the subfields, like “$snow_depth_elevation_1 = get_post_meta( get_the_ID(), ‘snow_depth_’ . $i . ‘_snow_depth_elevation_1’, true );”.

        So I started with just the fields in the first lines of the code. Out of curiosity, I reloaded the page and queries felt from 146 to 72, which is a lot. This is because ACF makes 2 queries per field/sub-field, one for its reference and one for its value.

        As for images, if I recall correctly, queries were reduced only by 2, which seems to match the reference/value queries but not the image sizes. It seems to me that ACF has already fixed that.

        Another curious issue is when going for the specific sections (snow, slopes, lifts, etc.) and their subfields. I would think that the variety of ski slopes or lifts values would reduce queries by a lot (we’re talking Repeaters in Flexible Content here), but nope, just 2 queries-reduction for everything slopes and lifts. Then, you do the same for something theoretically “smaller” or “simpler” like a simple text subfield in a simple field (not a Repeater), and you get 4 or 5 less queries.

        So all in all, just using get_post_meta() for the initial fields makes for a 50% reduction of your queries. Then it is step by step through all your subfield complexity. Sometimes you get 2 less, sometimes 5 when not expecting them.

        Thanks again.

  30. 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

  31. 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.

  32. 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:

      • 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.

  33. 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

  34. 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:

      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 ).

      • Carles says


        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!

  35. Carles says


    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?


    • 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.

  36. 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:
      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`.

  37. 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…

  38. 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

  39. Vasili says

    Hi Bill

    Thank you for this post.

    I love ACF and i think it’s a great tool, but to bad it was not built to be used with get_post_meta() as a default. I was able to reduce a lot of queries by re-writing all fields values to get_post_meta() on my two music sites.

    I also have fields in widgets and i now see that each field, when used with get_field(), creates a minimum of 5 queries. Unfortunately i was not able to make it work with get_post_meta() as ACF field in widget require widget id as a second parameter which is not possible with get_post_meta() and i always get an empty string.

    Did you have a chance to check how ACF field can be pulled in widget with get_post_meta()?

    • Bill Erickson says

      get_post_meta() is only used for post metadata. I haven’t used ACF fields in widgets before, but I assume ACF is storing that as options. Take a look at the wp_options table to see how the data is stored, and then you can access it using get_option().

      • Vasili says

        Thank you for your reply.

        I was able to change field value call from get_field() to get_option() using this syntax:

        echo get_option( 'widget_' . $args['widget_id'] . '_field_name' );

        The only thing is that now the value is not changing via the Customizer, only after i hit the “Save & Publish” button and refresh the page on the front end.

  40. Damien Carbery says

    I changed my get_field/the_field calls for a ‘gallery’ field type. I use wp_prepare_attachment_for_js( $attachment_id ) to get an array with all the image info (alt, title, url, sizes).
    When I disable ACF the ‘featured-image’ size disappears from the list of available sizes despite being in the list returned by get_image_sizes(). As soon as ACF is enabled it’s back!

    So, while I have been able to change to WordPress API functions (query count dropped from 58 to 41 on single post and 92 down to 53 on category page), I cannot disable ACF on the front end. Bizarre and frustrating.

    • Bill Erickson says

      I’ve never used wp_prepare_attachment_for_js(). Why use that instead of wp_get_attachment_image( $attachment_id, $size ) to generate actual markup, or wp_get_attachment_image_src( $attachment_id, $size ) to get all the pieces?

      • Damien Carbery says

        The array returned by wp_prepare_attachment_for_js() had everything I needed, including title and alt. I thought that I had tried the wp_get_attachment_image* functions and that they didn’t give me enough info, but I’ don’t see them in my svn log history.

        I have changed my code to use them. It’s even easier to read now. And it works when ACF is disabled. Down to 38 queries and definitely faster (~1.5sec vs 2.1 with get_field()).
        Thanks for the function suggestion.

  41. Michael says

    I wonder, is there any real benefit of bothering doing this. Since users on the front end should be hitting a html cache version anyway. Those queries run only once.

    • Bill Erickson says

      The bigger issue is what happens to your site if/when ACF gets deactivated. Your options are:

      1. Use ACF functions like get_field(), and have your site fatal error if the plugin is deactivated.
      2. Wrap every custom field area in if( function_exists( 'get_field' ) ) which prevents the white screen but nothing shows up when ACF is deactivated.
      3. Use core WP functions like get_post_meta(), no issue if plugin isn’t active

      It also makes it easier to transition your code to another metabox solution in the future. If you later decide to use Carbon Fields or CMB2, you migrate the data and update the meta keys, but the underlying functionality stays the same. If you’re using plugin-specific functions you’ll need to rebuild more.

      It’s not a big deal either way. It’s unlikely the client will deactivate ACF or that you’ll need to migrate between metabox plugins.

      I frequently with all three metabox plugins (I prefer Carbon Fields but stick with ACF/CMB2 if the site already uses it) and so it’s easiest for me to build general code that works with any solution rather than leverage their plugin-specific functions.

      I typically use my own helper function, ea_cf(), for retrieving a custom field. Then if the metabox solution changes I can update the functions in there, once, and everything in the theme just works, rather than updating everything in the theme.

      • Michael says

        I don’t have problems 1 or 2 cos i don’t deal with clients but i confess keeping queries at a minimum is becoming some sort of fetish for me, it’s fun to watch.

        I keep reading “ACF doesn’t scale”. I guess unless you aren’t doing stupid things like filter by custom fields “conditions” you are good.

        But this intrigues me, i want to see how much total time can be saved disabling ACF on front end. I didn’t got rid of all get_field yet because logic needs some tweaks.

        Maybe i’m doing this the dumb way but what i did was a single database call for:
        $all_fields = get_post_meta($post_id);

        And then i just hand pick what i was displaying before from my big array:
        $manufacturer = $all_fields[‘manufacturer’][0];

        True It gets tricky on repeater and gallery and for loops but it works.

        What do you think of this approach? i confess i didn’t pay much attention to your examples but i think you do 1 get_post_meta for every get_field.

        • Bill Erickson says

          The metadata attached to all posts in the main loop (ex: 10 posts on archive, 1 post on single post view) is cached. Every call to get_post_meta() does not result in another database query – it just does what you’re doing manually.

          I believe (but have not tested) that when you run get_post_meta() for a post that hasn’t been cached yet, it retrieves all that post’s metadata, and any additional get_post_meta() calls to that post are also using cached data.

          There should be no performance . difference between get_post_meta( $id, 'manufacturer', true ); and $meta = get_post_meta( $id ); $manufacturer = $meta['manufacturer'][0];

          • Michael says

            Glad to hear ‘cos my brain is pretty limited and it was getting confusing (i’m not a programmer). So i’m gonna finish up with get_post_meta then. Thanks for the great post and even replying to my babbling! Cheers!

  42. Michael says

    I got stuck on the gallery field. Well not really stuck, i was able to replace each of ACF array values with WP functions in doing so, the query number goes to the roof, except now is core doing them. I was pulling these values from the gallery array:


    Getting these from WP functions is more expensive (ACF array is VERY complete).
    So i guess there foes my dream of disabling ACF on front end.-
    PS: i’m using the blueimp lightbox which i think is pretty cool 😛

  43. Clay Teller says

    Hey Bill, after reading this, you’ve gotten me interested in making the switch from ACF Pro to Carbon Fields. On the Carbon Fields website (, implicit in their “Comparison with other plugins” section is that Carbon Fields offers a “flexible content alternative”. If that’s true, it would probably seal the deal for me switching over. Haven’t found in their documentation what the flexible content alternative is though. Do you know? Thanks!

  44. K says

    Curious – how does the Local JSON affect the overall performance (positively?) — because, truth be told, as much as I love performance and speed, in most cases ACF makes things happen quickly and efficiently and the websites run fine. I mean the added value of the ACF backend for Editors is still overpowering the performance hit in my opinion, but I am sensitive to it and I want to do better. Do you have a way of benchmarking / testing with Local JSON in tow?

    • Bill Erickson says

      I really need to update this post. I’ve heard that ACF has fixed most of the performance issues that caused the slow load time in the tests I ran for this post (published 4 years ago), but I haven’t re-run the tests.

      I’m adding it to my to-do list 🙂

      • Jamie says

        Hi Bill

        I’m just starting to use your method here to implement ACF’s instead of the get_field.

        So far I’ve got a fair bit working using your code examples in this post.

        But for the likes of me I can’t work out how to get images from the ACF gallery field using the get_post_meta. (to show a list of images)

        Thought you might have a code snippet handy? I can learn from that.


        • Bill Erickson says

          I’m pretty sure the gallery field stores an array of attachment ids. So $images = get_post_meta( get_the_ID(), 'my_gallery', true ); should get you something like $images = array( 123, 456 );

          In that case, you can loop through them and call wp_get_attachment_image() like so:

          foreach( $images as $image ) {
          	echo wp_get_attachment_image( $image, 'medium' );
  45. Andy says

    Hypothetically , what happens if a client deletes ACF wanting to remove all the data and discovers all the data is still showing on the front end ?

    They’ll find nothing in the back-end to edit or delete as the boxes are gone. What’s the workload in correcting that.

    I understand the need for speed in query counts , if that’s what’s required why install ACF to start with or why stick with it if it’s so bad ? It can be included in the theme or plugin (or even MU) , therefore cannot be deleted.

    if you use if( function_exists( ‘get_field’ ) ) or if( !class_exists(‘acf’) ) then no white screen anyway.

    • Bill Erickson says

      That’s an interesting thought. I think that’s a feature, not a bug.

      I would never expect nor want my metadata to be deleted when a metabox plugin is removed. The removal might be temporary, like when trying to find a plugin conflict on site, or downgrading from a beta version to the stable release.

      You might be transitioning from one metabox solution to another. I’ve often migrated metadata between ACF, Carbon Fields, and CMB2 depending on the project requirements.

      At it’s core, the metabox plugin’s job is to make metabox creation easier. It is not responsible for the actual metadata storage – that’s a core WordPress feature.

      If I remove an events calendar plugin from my site, I don’t expect it to delete all the events in the database. I expect it to leave the data untouched.

      Yes, you can resolve the “white screen” issue with function_exists(). I’m doing that currently on a site that depends upon ACF on the frontend (we’re using it for building Gutenberg blocks).

      The purpose of this post was to:
      a) Point out a frontend performance issue I found on sites using ACF
      b) Illustrate how to access metadata created by ACF using core WordPress functions
      c) Explain ACF’s naming structure for different data types

      Whether you use ACF across the whole site, only in the backend, or not at all, is a personal decision and one based on the requirements of the project.

  46. Geert van der Heide says

    Like Bill Erickson mentioned above, the performance hit from ACF has gone way down in the last few years. I was curious how much of an influence ACF has on the loading of my current project, a site that uses several ACF fields (including a flexible content field) on most pages. The result of deactivating ACF on the front-end and replacing its function calls with native functions was barely noticeable. Tested with Query Monitor, and both the loading time and the number of queries don’t go down much.

    It’s probably still beneficial to set your image fields to output the attachment ID only, and to then use wp_get_attachment_image_src or similar to retrieve the image URL, width and height. But anything else doesn’t seem worth it. So just use ACF functions or WP functions as desired.

  47. Stephen says

    I know this article is quite old and i used a few years ago. just came back to it after needing to revise an excessive use of ACF on the front end that what causing all sorts of issues. Should of commented when first used but should say this article in Timeless. It well written and extremely informative delivering immediate results in page performance.

    I have a block manager i wrote before guttenberg came out. It enables users to create blocks that are associated with templates. Each block can manage approximately 10 fields some of which are repeaters. In my case, was written to accomodate 4 or 5 blocks. Of course the client is now creating 10-12 blocks on a page. That equates to perhaps 100 or so get_fields. Removing get_field reducing the query considerably especially if you have object cache as the meta fields are stored there.

    I am though messing with an alternative way of handling. I wondered if possibly better to collect all the page meta in one hit with get_post_custom and then reference the array directly. I managed to reduce the querying on the database again quite considerably so when cache has been cleared i don’t have again as many queries from get_post_meta where the value is yet to be stored.

    just for peoples interest, starting to review CMB2

Leave A Reply