WordPress developer, consultant, nerd

Reducing non-essential meta data when using Advanced Custom Fields

If you are like me and have worked heavily with Advanced Custom Fields on sites with a lot of posts, you’ll have likely found yourself looking at a very large post meta table. Analysing this data can be kinda boring, but whilst doing so, I noticed a lot of simple boolean-like values were stored, even when the value was false.

To illustrate this, I’ve set up a post with a single ACF field of type true/false – the field name being my_true_false – and saved the post with the field unchecked:

Note the two records entered by ACF for the one field: one record being the value, the other being the field key reference. You might not consider this to be a problem at this scale, but imagine having 1000 posts, each containing 5 true/false toggles. That’s 10,000 records in your post meta table, just for the one field!

The key realisation

I’ve been working heavily with ACF lately while developing the ACF Custom Database Tables plugin, so I’m well familiar with many of the available hooks and filters. Whilst working on some site optimisations on a site with a large(40,000 posts) database, a few key points bought me to a realisation:

  1. ACF provides a filter for 3rd parties to modify a field’s value before it is saved. If this value is null, ACF won’t store it and will actually remove the value from the database if already there.
  2. ACFs get_field() function will return a null value where a value hasn’t been stored.
  3. In the case of an ACF true/false field, a false setting would return FALSE boolean, which loosely equates to null where a non-strict comparison operator is used (i.e; == instead of ===)

These points led me to the realisation that, if a true/false value was not in the database, we can (in most cases) presume it to be false, negating the need for us to store the value in the first place.

So, what happens if we only store true values?

I ran a test and found that if I set up a simple filter that evaluates the value of a field as falsy and returns null in that situation, the data won’t be saved to the meta table:

/**
 * This prevents the setting from hitting post meta unless it is set to true. 
 * It will also delete the value in the database on next post save, if the value
 * has already been saved previously.
 */
add_filter( 'acf/update_value/name=my_true_false', function ( $value ) {
	return $value ?: null;
} );

The above snippet works because ACFs 3rd party customisation filters are followed by a conditional check that prevents the saving of the meta where the value is null and also deletes any previously saved value for that field. For a true/false field, it makes sense not to store the data because the absence of that data will result in a null return value when using ACF functions such as get_field('my_true_false').

Taking a more generic approach

The above snippet also specifically targets a field by name. If you have a scenario where you can set this up for all true/false fields in one hit, you could use the following:

/**
 * This will intercept all true/false fields on site. 
 * Test thoroughly and use with caution.
 */
add_filter( 'acf/update_value/type=true_false', function ( $value ) {
	return $value ?: null;
} );

You would definitely want to test this thoroughly, however, as you might find yourself having issues where you may have explicitly checked for a boolean FALSE on return of a call to get_field(). i.e;

if ( get_field( 'my_true_false' ) === false ) {
	// this won't run if the data isn't in the table
}

if ( get_field( 'my_true_false' ) == false ) {
	// this will run
}

Conclusion

By thinking outside of the box, we can make some of these micro-optimisations work for us and cut down on the amount of data we store. This kind of trick really only pays off when dealing with large data sets, but it goes to show that a familiarity with the code behind the systems you work with can often lead to inadvertent solutions to problems we may have previously just come to accept or had attempted to handle in some other way.

I’m already considering additional experiments where I might choose not to store a default value for a field and use other ACF hooks to populate that default value where it hasn’t already been stored in the database. Something for a future post, perhaps.

Thanks for reading.