8 snippets to modify ACF behavior

How to customize the ACF logic when developing a WordPress plugin or theme?

Looking to level up your WordPress custom development skills? Let’s discover some of the best ACF’s hooks to alter its behavior.

This post is a translation of an original tutorial I wrote on my French blog (Trucs & astuces ACF pour développeurs WordPress).

I don’t know if you are a regular user of this WordPress plugin, but I love Advanced Custom Fields. In my daily life as a freelance WordPress developer, it saves me a lot of time! Creating metaboxes, inserting simple or complex fields in them, managing their conditional display… Thanks to ACF, I avoid a lot of lines of code to get a quality result.

But sometimes, the ACF interface does not let you achieve complex and dynamic behaviors. Luckily, Elliot Condon – the developer of ACF – has placed well-thought-out hooks in its plugin codebase. Let’s discover here some of ACF’s filters and actions to customize its behavior.

The plugin containing all these tricks, as well as the configuration of the metabox and the fields used, can be downloaded here for free.

Introduction

WordPress developer tips and tricks: change Advanced Custom Fields behavior with hooks
Alter ACF logic with hooks and add your own custom logic

5 years ago I published articles and videos about using a library to define fields and metaboxes in WordPress about using a library to define field groups and fields in WordPress via code… a lot of code!

Today, my WordPress development is greatly facilitated thanks to the use of the excellent Advanced Custom Fields extension. I use it 2 times out of 3 in my projects requiring the creation of field groups, fields and option pages.

ACF: Ah See What?

Quick summary of ACF for those who don’t know it: ACF is a plugin for creating metaboxes and fields in WordPress via an efficient visual interface integrated into the WordPress back-office.

ACF offers a clean GUI to create groups of fields on posts, users, terms, pages...
ACF offers a clean GUI to create groups of fields on posts, users, terms, pages…

It’s simple, we can do a lot of things thanks to it, sparing us several hundred lines of code to achieve the same ends:

  • you can easily create your field groups and define where to display them
  • we add fields to our groups in a few clicks and decide to display them according to certain conditions
  • you can choose among many types of fields (text, number, images, relation with posts/taxonomies)
  • you can create complex field structures such as field groups, tabs, accordions
  • we can make field compositions thanks to the Flexible Contents, and duplicate fields thanks to the Repeater Fields
  • we can easily create options pages to efficiently configure our WordPress sites
  • …and much more

The list is long but you get the point: ACF is a gold nugget for any good WordPress developer who doesn’t want to reinvent the wheel.

An extension “hookable” by filters and actions

I can already hear the purists grumbling: “yeah yeah ok that’s fine, but using a visual interface to create fields, you’ll soon be limited and you’ll never be able to go as far as with fields defined in code, and I’m a developer, I’ll never use a plugin to save my meta fields”. Well, think again!

The core of the plugin is based on an intelligent use of actions and filters, making ACF a champion in customizing its own logic. It is in no way limited by the definitions that can be set up in its visual interface.

As proof, I worked on the Miam.Pizza website and relied exclusively on ACF for the dozens of metaboxes and front-end forms available on the site. Each of these forms is largely customized via code for, among others:

  • modify the display conditions of certain ACF fields and their parameters,
  • modify some field values before saving,
  • create custom validations of fields/forms,
  • perform other actions after submitting an ACF form

So, having used it extensively over the last few years, here are a few tips and tricks to help you propel your ACF usage and customize it with these filters and actions. Make your choice: video or text?

Tips and tricks for a customized WordPress development with ACF

Saving JSON ACF files in your plugin

<?php

// Article/tutoriel complet sur https://mosaika.fr/astuces-developpement-acf/
// Documentation officielle https://www.advancedcustomfields.com/resources/local-json/

define('MSK_ACF_PATH', plugin_dir_path(__FILE__));

/**
 * Enregistrer la config de ses metaboxes & champs ACF dans un dossier de son plugin/thème
 * Filtre : acf/settings/save_json
 */
function msk_acf_change_json_save_point($path) {
	$path = MSK_ACF_PATH . 'acf-fields';
	return $path;
}
add_filter('acf/settings/save_json', 'msk_acf_change_json_save_point');


/**
 * Charger la config de ses metaboxes & champs ACF dans un dossier de son plugin/
 * Filtre : acf/settings/load_json
 */
function msk_acf_change_json_load_point($path) {
	$path[] = MSK_ACF_PATH . 'acf-fields';
	return $path;
}
add_filter('acf/settings/load_json', 'msk_acf_change_json_load_point');

ACF offers by default a very handy feature called Local JSON: by creating a folder named acf-json at the root of your theme, ACF will save a JSON file each time you save one of your field groups.

The configuration of this group and the fields it contains is therefore on the one hand stored in this JSON, but also loaded from it; this avoids calls to the database for ACF to load these fields and thus gains in performance.

“Yeah, but, uh, I myself am developing a WordPress plugin with ACF.”

No worries! For WordPress developers who don’t need to save these JSON ACF configuration files in the theme, fortunately we can tell ACF where to save our metaboxes and fields. If these are so vital to your plugin:

  • create an empty folder at the root of the WordPress plugin you are developing
  • use the acf/settings/save_json filter to tell ACF where to write JSON files for each field group record
  • use the acf/settings/load_json filter to tell ACF where to read JSON files and load the metaboxes stored in them

Once these configuration files are synchronized with a new folder of your plugin, it will automatically fill up with .json files every time you create or update your field groups.

This is very handy if, for example, you drop the source of your WordPress extension into Git: another developer who retrieves your repo will then see in his ACF interface the ability to synchronize new groups of fields from those .json files.

Dév WordPress et ACF : synchroniser des groupes de champs grâce aux fichiers JSON
Sync available in the ACF fields back-office

Defining an ACF field as a read-only field

<?php

// Article/tutoriel complet sur https://mosaika.fr/astuces-developpement-acf/
// Documentation officielle https://www.advancedcustomfields.com/resources/acf-load_field/


/**
 * Désactiver un champ (lecture seule)
 * Filtre : acf/load_field
 */
function msk_acf_disable_field($field) {
    $field['disabled'] = true;
    //$field['readonly'] = true;

    return $field;
}
add_filter('acf/load_field', 'msk_acf_disable_field');
//add_filter('acf/load_field/name=annonce_prix_avec_commission', 'msk_acf_disable_field');
//add_filter('acf/load_field/type=password', 'msk_acf_disable_field');
//add_filter('acf/load_field/key=field_5a9af8d5250dd', 'msk_acf_disable_field');

We use here the acf/load_field filter which gives us the possibility to modify the parameter of a field before it is loaded by ACF. We can thus modify its properties such as its label, the fact that it is required or not, its default value, its CSS ID or class, the width of the field, etc.

Setting the $field['disabled'] property to true indicates to ACF that you do not want to make the field editable. It will be visible, but disabled and therefore impossible to edit.

This can be useful to propose a data to the user but not to give them the possibility to modify this data. Here, we deactivate our “Price with commission” field, which will later be dynamically calculated when saving an ad: the user will then see the real price of his product on our site in the field concerned, but will not be able to edit it.

Note that the $field[‘readonly’] parameter does exactly the same thing!

Extra tip

Notice the dynamic syntax of many filters and ACF actions:
– the acf/load_field filter targets all fields,
– the filter acf/load_field/name=abc targets all fields with abc as their name
– the filter acf/load_field/key=xyz targets all fields with xyz as their name
– the filter acf/load_field/type=text targets all text type fields

Dynamically populate an ACF drop-down menu

<?php

// Article/tutoriel complet sur https://mosaika.fr/astuces-developpement-acf/
// Documentation officielle https://www.advancedcustomfields.com/resources/acf-load_field/


/**
 * Charger dynamiquement les choix d'un menu déroulant
 * Filtre : acf/load_field
 */
function msk_acf_populate_year_field($field) {
	$years = [];
	$max_years = 5;

	for ($i = 0; $i <= $max_years; $i++) {
		$date = date('Y') - $i;
		$years[$date] = sprintf('Acheté en %d', $date);
	}
	
	$field['choices'] = $years;

	return $field;
}
add_filter('acf/load_field/name=annonce_date_achat', 'msk_acf_populate_year_field');

Once again, the acf/load_field filter is used, but with a view to modifying another parameter of a field. How to dynamically fill the options of a <select> field created by ACF?

By modifying the $field['choices'] key, we will be able to provide ACF with an associative array to fill the drop-down menu with the values we want. In this array, the keys will be the values of the <options> and they will be associated to the post meta and stored in the SQL database. The values of the table are presented in the drop-down menu to be selected by the user.

Thanks to this logic, it will be possible to propose variable data in the drop-down menu, for example to :

  • choose a value according to the logged-in user,
  • choose a value according to a global setting at the site, 
  • populate the drop-down menu with data from a third-party site provided via an API

Practical, isn’t it?

Modifying variables in an ACF field of type Message

<?php


// Article/tutoriel complet sur https://mosaika.fr/astuces-developpement-acf/
// Documentation officielle https://www.advancedcustomfields.com/resources/acf-load_field/


/**
 * Injecter des données dynamiques dans les champs de type message
 * Filtre : acf/load_field
 */
function msk_acf_variables_in_message_fields($field) {
	$field['message'] = str_replace(
		['%date_debut%', '%date_fin%'],
		[date('Y') - 5, date('Y')],
		$field['message']
	);

	return $field;
}
add_filter('acf/load_field/type=message', 'msk_acf_variables_in_message_fields');

Decidedly, we will never let go of the acf/load_field filter again! Here, we have inserted “marker words” (variables) in a Message field, as below:

WordPress & ACF : placeholders in a message field to dynamically modify its content
WordPress & ACF : placeholders in a message field to dynamically modify its content

Thanks to a clever use of str_replace(), we will be able to modify the $field[‘message’] parameter so that these marker words are replaced by dynamic variables.

We have previously dynamically populated a drop-down menu with the last 5 years (2018 to 2013). As far as the message explaining this, before the drop-down menu, reflects the same logic: even better, it will be done dynamically and we won’t have to edit our field every year!

Validate ACF fields by comparing their values with each other

<?php

// Article/tutoriel complet sur https://mosaika.fr/astuces-developpement-acf/
// Documentation officielle https://www.advancedcustomfields.com/resources/acf-validate_value/


/**
 * Comparer la valeur de 2 champs pour accepter/refuser leur validation
 * Filtre : acf/validate_value
 */
function msk_acf_compare_2_fields_values($valid, $value, $field, $input) {
	if (!$valid) return $valid;
  
  // get_acf_post_value() est une fonction helper définie plus bas
	$marque = get_acf_post_value('annonce_marque');
	$modele = get_acf_post_value('annonce_modele');
	
	if (empty($marque) && empty($modele)) {
		return 'Il nous faut au moins la marque ou le modèle du produit';
	}

	return $valid;
}
//add_filter('acf/validate_value', 'msk_acf_compare_2_fields_values', 10, 4);
add_filter('acf/validate_value/name=annonce_marque', 'msk_acf_compare_2_fields_values', 10, 4);
add_filter('acf/validate_value/name=annonce_modele', 'msk_acf_compare_2_fields_values', 10, 4);


/**
 * Recherche une clé dans une array
 */
function search_by_key($array, $key) {
	$results = array();

	if (is_array($array)) {
		if (isset($array[$key])) {
			$results[] = $array[$key];
		}

		foreach ($array as $subarray) {
			$results = array_merge($results, search_by_key($subarray, $key));
		}
	}

	return $results;
}


/**
 * Recherche d'une valeur d'un champ ACF via son nom (et non sa clé)
 */
function get_acf_post_value($slug = false, $array = false) {
	if (!$array) $array = $_POST['acf'];

	$key = acf_get_field($slug)['key'];
	$results = search_by_key($array, $key);
	
	if (count($results) == 1) return reset($results);
	
	return $results;
}

What I love to be able to do with ACF: the validation of a field goes through a filter, so we can freely tell ACF to consider the value of a field as valid, or tell it that it is not valid while indicating the error message that will be displayed in red above the field concerned.

<?php
function msk_acf_validate_value($valid, $value, $field) {
    // Déjà invalidé par une autre logique en amont ? On renvoie le message d'erreur déjà présent dans $valid
    if (!$valid) return $valid;

    // Si la valeur de notre champ est inférieur à 10, OK : on renvoie true
    if ($value < 10) {
        return true;

    // Si la valeur de notre champ est supérieur à 10, pas bon : on renvoie un message d'erreur
    } else {
        return 'Nous voulons une valeur inférieure à 10 !';
    }
}
add_filter('acf/validate_value', 'msk_acf_validate_value', 10, 3);

All this happens in the acf/validate_value filter. The function we will assign to this hook will be able to receive 3 very useful parameters:

  • $valid contains the current status of the ongoing validation. If the field is not already validated before our function intervenes, we might as well (perhaps) ignore our own validation.
  • $value is the value indicated in the field in question. This is the one that will be analyzed to do our audits and test more specific things in our code, such as:
    • if an e-mail address does not already exist in our user base
    • if a password is at least 8 characters long
    • if a telephone number is in the correct format
  • and $field, less used, contains the parameters of the field in question, as in the acf/load_field filter

This filter waits for the value true to consider the field as valid, and a string if it is not valid: this string will be used by ACF as an error message and displayed in a red tooltip above the field.

Access the values of other fields when validating a specific field

In our case, we try to ensure that at least one of our two Brand or Model fields is not empty (so that at least one of them is filled). We will therefore use the same hooked function on the validation of these two fields, thanks to the 2 dynamic filters acf/validate_value/name=brand_announcement and acf/validate_value/name=model_announcement.

The function has the value of the field being analyzed through the variable $value. But it is also possible to access all the values of the other fields of the form sent, stored in the global variable $_POST.

Form field values are available in the $_POST['acf'] variable

You will therefore find in the $_POST[‘acf’] variable an associative array with the values of the other fields in the form, each key in the array corresponding to the key of the field that ACF has assigned to the field.

Retrieve the value of another ACF field by specifying its name

If you’re like me and don’t really like working with the unreadable keys that ACF assigns to each of these fields, use the get_acf_post_value( $name ) function that you’ll find in the snippet above.

This function makes it possible to find in the data $_POST[‘acf’] the value of a field whose name is given, and not its key.

In our case, we can easily retrieve for example the brand of the product entered by the user thanks to get_acf_post_value( 'brand_advertisement' ). I find this much more natural and logical; we avoid having to find the ACF identifier of the field and we keep a certain readability in our code.

Clean up ACF WYSIWYG field values before their SQL saving

<?php

// Article/tutoriel complet sur https://mosaika.fr/astuces-developpement-acf/
// Documentation officielle https://www.advancedcustomfields.com/resources/acf-validate_value/


/**
 * Nettoyer/sécuriser la valeur d'un champ WYSIWYG
 * Filtre : acf/update_value
 */
function msk_acf_sanitize_wysiwyg_fields($value, $post_id, $field) {
	if (is_array($value)) {
		return array_map('wp_kses_post', $value);
	}

	return wp_kses_post($value);
}
add_filter('acf/update_value/type=wysiwyg', 'msk_acf_sanitize_wysiwyg_fields', 10, 3);

Once all the fields considered valid by ACF, the values of these fields are saved in the database. And once again, ACF gives us the possibility of intercepting its values just before they are recorded, in order to eventually modify them thanks to the acf/update_value filter.

As mentioned in the documentation, this filter has 3 parameters that we can use in our function:

  • $value contains the original value of the field, which we may want to modify.
  • $post_id gives access to the ID of the post being edited
  • and $field gives us the details of the field being saved

This information will help us to decide whether or not to modify the value entered by the user in order to, for example, clean it and make it safe and secure before it is saved in the database.

Save other data when ACF saves the post

<?php

// Article/tutoriel complet sur https://mosaika.fr/astuces-developpement-acf/
// Documentation officielle https://www.advancedcustomfields.com/resources/acf-save_post/


/**
 * Enregistrer la variable d'un champ dans un autre champ du post
 * Filtre : acf/save_post
 */
function msk_acf_save_another_field($post_id) {
	if (get_post_type($post_id) != 'msk_annonce') return;

	// Titre (post_title)
	$marque = get_field('annonce_marque', $post_id);
	$modele = get_field('annonce_modele', $post_id);
	$espace = (!empty($marque) && !empty($modele)) ? ' ' : '';

	$title = sprintf(
		'%1$s%2$s%3$s',
		$marque,
		$espace,
		$modele
	);

	wp_update_post(
		[
			'ID' => $post_id,
			'post_title' => $title
		]
	);

	// Prix (meta annonce_prix_avec_commission)
	$prix = (float)get_field('annonce_prix', $post_id);

	if ($prix > 0) {
		$prix_commission = round($prix * 1.25, 2);
		update_field('annonce_prix_avec_commission', $prix_commission, $post_id);
	}
}

add_action('acf/save_post', 'msk_acf_save_another_field', 20);

Need to execute actions when ACF saves post data? The acf/save_post hook is made for that! It provides us with the ID of the post ($post_id) that has just been saved.

Pay attention to the priority of the action you define: below 10, ACF will not have done its job of saving the values. So, you still have the old meta values associated with the post in the database, the new ones being accessible in the global variable $_POST.
With a priority higher than 10, the post is freshly registered and its metas are up to date. So, use the right priority according to your usage: below 10 if you need to access the old values of the post’s metas fields before they are overwritten by the new ones, and above 10 to get the most up-to-date data.

This is one of the latest hooks in ACF: the user has sent the post edit form; the fields are validated and the data is saved. It is therefore a very useful action to execute other logics once a post is saved, such as:

  • send an e-mail to the administrator to inform him/her of the publication of a new announcement
  • delete and recreate transient data, such as global statistics on published classified ads
  • add the seller’s e-mail to a mailing list
  • or, as in our case, save new data in the post edited to update the product title or included commission price

Note that the acf/save_post action runs each time a post with ACF fields is saved. It is therefore important to check the content type of the post being edited (with get_post_type($post_id)), to ensure that you execute your logic only when necessary. Beware of unpleasant surprises!

Do not save the value of an ACF field

<?php

// Article/tutoriel complet sur https://mosaika.fr/astuces-developpement-acf/
// Documentation officielle https://www.advancedcustomfields.com/resources/acf-update_value/


/**
 * Comparer la valeur de 2 champs pour accepter/refuser leur validation
 * Filtre : acf/update_value
 */
function msk_acf_do_not_save_field_value($value, $post_id, $field) {
	return null;
}
//add_filter('acf/update_value/type=password', 'msk_acf_do_not_save_field_value', 10, 3);
add_filter('acf/update_value/name=annonceur_mot_de_passe', 'msk_acf_do_not_save_field_value', 10, 3);

To end this article, a handy little trick to force ACF not to save any meta for a specific field. Once again, we use the acf/update_value filter but by “returning nothing” (with return null), we tell ACF not to record anything in the database.

Handy if you have a password field to use only when creating a user account in WordPress: once the user is created, don’t store the password in plain text in a meta post!


What about your discoveries? If you have any interesting WordPress ACF tips, feel free to share them with us in the comments section!


You liked this content?

Make sure to share it on your social networks!