Recently I was working on a client project where site authors had the issue of post types in an ACF Relationship field not being distinguishable from one another. They all had a parent type, which gave the child type more context, but that parent type was not shown in the field. Additionally the high volume of items made finding the desired selection difficult.
I used ACF’s Javascript API for the first time to address this concern.
In this example, we have two post types, Brands and Products. When Products are all available in a relationship field, it’s not clear what brand they belong to. I’ve added an additional Select field to narrow the results in the Relationship field to strictly those belonging to the selected brand.
Below is the field settings for the Select. In this example, I’ve entered the Post IDs for the value, but this can also be done dynamically with ACF hooks.

I’ve encapsulated this into a class, so within the init() method, enqueue the Javascript.
class ExampleCode {
public function __construct() {
// Example: Dynamically populate ACF Relationship Field
// Based on selection of a parent Select
$this->register_post_type_brands();
$this->register_post_type_products();
}
public function init() {
// Enqueue JavaScript
add_action('acf/input/admin_enqueue_scripts', array($this, 'acf_enqueue_acf_field_scripts'));
// Ajax for Narrow ACF Field by parent
add_action( 'wp_ajax_get_products_by_brand', array($this, 'get_products_by_brand' ) );
// Add styles to the acf fields in wp admin
add_action('acf/input/admin_head', array( $this,'add_acf_admin_styles'));
}
}
$filter_acf_example = new ExampleCode();
$filter_acf_example->init();
Use ACF’s Javascript API action ‘new_field’ with the name of your Select field.
/**
* Filter on Select fields that allow for picking a Product
* and narrow the results of relationship field to that specific brand
*
*/
const filterProductsByBrand = function (field) {
const brandComponent = field.data.name;
if (typeof window.jQuery !== 'undefined') {
// Reset the filter to 'None Selected', don't save selections
field.val('none');
};
if (typeof acf !== 'undefined') {
acf.addAction('new_field/name=narrow_by_brand_select', filterProductsByBrand);
}
Add a listener for the change event and ajax post your selected choice ID. I’ve also used ACF’s API to show a Loading warning for user feedback. Here is the final JavaScript.
/**
* Filter on Select fields that allow for picking a Product
* and narrow the results of relationship field to that specific brand
*
*/
const filterProductsByBrand = function (field) {
const brandComponent = field.data.name;
if (typeof window.jQuery !== 'undefined') {
// Reset the filter to 'None Selected', don't save selections
field.val('none');
// add on change event to this select field
field.on('change', 'select', function (e) {
field.showNotice({ text: 'Loading', type: 'warning', dismiss: false });
e.preventDefault();
const selectedVal = field.val();
jQuery.ajax({
type: 'post',
url: ajaxurl,
data: {
nonce: acf_field_admin_js.ajaxnonce,
action: 'get_products_by_brand',
brand_id: selectedVal,
},
success: function (resp) {
// str = JSON.stringify(resp);
// console.log(str);
if (brandComponent === 'narrow_by_brand_select') {
jQuery('#acf-product-relationship-select .selection .choices ul.acf-bl.list').html(resp.html);
}
field.removeNotice();
},
error: function () {
field.removeNotice();
field.showNotice({ text: 'Filter failed. Please try again.', type: 'warning', dismiss: false });
},
});
});
}
};
if (typeof acf !== 'undefined') {
acf.addAction('new_field/name=narrow_by_brand_select', filterProductsByBrand);
}
Below is a simplified version generating the request response. Additionally I’ve added some styles to provided more user feedback about the data choices updating.
/**
* Enques script for dynamic js acf relationship field
* Sets up nonce for ajax in same file
* */
public function acf_enqueue_acf_field_scripts() {
wp_enqueue_script( 'acf-field-admin-js', get_theme_file_uri( '/assets/js/acf-field-admin-js.js'), array(), '1.0.0', true );
wp_localize_script( 'acf-field-admin-js', 'acf_field_admin_js',
array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'ajaxnonce' => wp_create_nonce( 'ajax_validation' )
)
);
}
public function get_products_by_brand() {
$brand_id = $_POST['brand_id'];
$products = $this::get_child_products_of_brand($brand_id);
$return = array(
'results' => $products['data'],
'html' => $products['html'],
);
wp_send_json($return);
die();
}
public function get_child_products_of_brand($brand_id){
$args = [
'post_type' => 'products',
'post_status' => 'publish',
'orderby' => 'publish_date',
'order' => 'DESC',
'posts_per_page' => 40,
'meta_query' => [
[
'key' => 'brand',
'value' => $brand_id,
'compare' => 'LIKE',
],
],
];
$products = new WP_Query($args);
if ($products->found_posts > 0) {
$products_found = $products->posts;
$html = '';
foreach($products_found as $product){
$product_title = esc_html__($product->post_title, 'text-domain' );
$product_set[] = [
'product_id' => $product->ID,
'product_title' => $product_title,
];
$html.='<li><span class="acf-rel-item product-filtered-acf" data-id="'.$product->ID.'">'.$product_title.'</span></li>';
}
return [
'data' => $product_set,
'html' => $html,
];
} else {
return [];
}
} // /END get_child_products_of_brand
/**
* Add styles to acf fields in admin
*
* **/
public function add_acf_admin_styles() {
?>
<style type="text/css">
.product-filtered-acf {
background-color: #fff1be;
}
.acf-relationship .list .product-filtered-acf:hover{
background-color: #3875D7;
}
</style>
<?php
}
The Relationship field now shows only the products matching the chosen brand.
