Creating Custom Location Rules for Advanced Custom Fields

See all of my location rules here: ACF Location Rules

Advanced Custom Fields is a powerful and user friendly plugin for creating metaboxes. One of the best features is the conditional logic you use to specify where a metabox appears. ACF refers to this as the “Location Rules”.

But what if there isn’t a location rule that matches your specific need? ACF can be easily extended with filters.

I’m going to show you how to create a custom rule (Page Ancestor), and add an option to an existing rule (“No Children” option on Page Type).

Building a Products Section

I’m working on a website that has a Products area. It’s only informational (not ecommerce) and the pages are pretty simple. I’m building it all using the “Page” post type.

It also supports multiple levels of categories. Some sections will just be Products > Category > Product Name, while others will be Products > Category > Subcategory > Product Name.

I’ve created two files in my theme: product.php for the single product view (if it has no children), and product-listing.php for the product listing view (if it has children). I’m using the following code in my theme’s functions.php file to use the correct template file:

<?php
/**
* Template Hierarchy
*
*/
function ea_template_hierarchy( $template ) {
if ( is_page() ) {
$ancestors = get_ancestors( get_the_ID(), 'page' );
$children = get_pages( array( 'child_of' => get_the_ID() ) );
// Product page has an ID of 25
if ( in_array( 25, $ancestors ) || 25 == get_the_ID() ) {
if ( !empty( $children ) ) {
$template = get_query_template( 'product-listing' );
} else {
$template = get_query_template( 'product' );
}
}
}
return $template;
}
add_filter( 'template_include', 'ea_template_hierarchy' );
view raw functions.php hosted with ❤ by GitHub

I need a metabox for managing product details, but only want it to appear on the single product pages, not the product listing pages.

Creating a Page Ancestors Location Rule

ACF has a “Page Parent” rule, but it only works for direct parents. If you’re on a grandchild page it will return false. I’m going to create a new rule called “Page Ancestor” that lets you select a page and will return true if the current page is an ancestor of the selected page at any level (child, grandchild, great grandchild…).

All of this code is going in my Core Functionality plugin. I created a file called acf.php to hold all of my ACF customizations.

Rule Type

First, I’m going to create the rule type. It’s going to be called “Page Ancestor” and be in the “Page” section:

<?php
/**
* ACF Rule Type: Page Ancestor
*
* @author Bill Erickson
* @see http://www.billerickson.net/acf-custom-location-rules
*
* @param array $choices, all of the available rule types
* @return array
*/
function ea_acf_rule_type_page_ancestor( $choices ) {
$choices['Page']['page_ancestor'] = 'Page Ancestor';
return $choices;
}
add_filter( 'acf/location/rule_types', 'ea_acf_rule_type_page_ancestor' );
view raw acf.php hosted with ❤ by GitHub

Now I have this option in my Location Rules:

Rule Values

We need to populate the rule values, the pages you can select as the ancestor. Since I want this to look and work just like the other page dropdowns, I copied the code for the ‘page’ rule values from here. We use a filter named acf/location/rule_values/{your rule type}:

<?php
/**
* ACF Rule Values: Page Ancestor
*
* @author Bill Erickson
* @see http://www.billerickson.net/acf-custom-location-rules
*
* @param array $choices, available rule values for this type
* @return array
*/
function ea_acf_rule_values_page_ancestor( $choices ) {
// Copied from acf/core/controllers/field_group.php
// @see http://bit.ly/1Xnx44g
$post_type = 'page';
$posts = get_posts(array(
'posts_per_page' => -1,
'post_type' => $post_type,
'orderby' => 'menu_order title',
'order' => 'ASC',
'post_status' => 'any',
'suppress_filters' => false,
'update_post_meta_cache' => false,
));
if( $posts )
{
// sort into hierachial order!
if( is_post_type_hierarchical( $post_type ) )
{
$posts = get_page_children( 0, $posts );
}
foreach( $posts as $page )
{
$title = '';
$ancestors = get_ancestors($page->ID, 'page');
if($ancestors)
{
foreach($ancestors as $a)
{
$title .= '- ';
}
}
$title .= apply_filters( 'the_title', $page->post_title, $page->ID );
// status
if($page->post_status != "publish")
{
$title .= " ($page->post_status)";
}
$choices[ $page->ID ] = $title;
}
}
return $choices;
}
add_filter( 'acf/location/rule_values/page_ancestor', 'ea_acf_rule_values_page_ancestor' );
view raw acf.php hosted with ❤ by GitHub
Rule Operator

I haven’t modified the rule operator, so it will use the defaults: is equal to and is not equal to. If you wanted to modify them, you can use the filter acf/location/rule_operators/

Rule Match

Finally, we need to specify the rule match code that runs when editing a page and decides if this metabox should display or not. We use a filter named acf/location/rule_match/{your rule type}. Remember that either operator can be used so you need to provide the logic for both:

<?php
/**
* ACF Rule Match: Page Ancestor
*
* @author Bill Erickson
* @see http://www.billerickson.net/acf-custom-location-rules
*
* @param boolean $match, whether the rule matches (true/false)
* @param array $rule, the current rule you're matching. Includes 'param', 'operator' and 'value' parameters
* @param array $options, data about the current edit screen (post_id, page_template...)
* @return boolean $match
*/
function ea_acf_rule_match_page_ancestor( $match, $rule, $options ) {
if ( ! $options['post_id'] || 'page' !== get_post_type( $options['post_id'] ) )
return false;
$ancestors = get_ancestors( $options['post_id'], 'page' );
$is_ancestor = in_array( $rule['value'], $ancestors );
if ( '==' == $rule['operator'] ) {
$match = $is_ancestor;
} elseif ( '!=' == $rule['operator'] ) {
$match = ! $is_ancestor;
}
return $match;
}
add_filter( 'acf/location/rule_match/page_ancestor', 'ea_acf_rule_match_page_ancestor', 10, 3 );
view raw acf.php hosted with ❤ by GitHub

Adding “No Children” option to “Page Type” Rule

You can modify existing Location Rules using the same filters as above if you know the name of the rule. You can dig through the ACF code to find it, or just “Inspect Element” in your browser and dive down in the select field.

Rule Values

Using the acf/location/rule_values/page_type filter I’m going to add “No Children” as one of the choices.

<?php
/**
* ACF Rule Values: Page Type
*
* @author Bill Erickson
* @see http://www.billerickson.net/acf-custom-location-rules
*
* @param array $choices, available rule values for this type
* @return array
*/
function ea_acf_rule_values_page_type( $choices ) {
$choices['no_children'] = 'No Children';
return $choices;
}
add_filter( 'acf/location/rule_values/page_type', 'ea_acf_rule_values_page_type' );
view raw acf.php hosted with ❤ by GitHub
Rule Match

Using the acf/location/rule_match/page_type filter I’m going to check for matches when ‘no_children’ is selected. Be sure to return the original $match rather than just true/false because there will likely be other rule matches that are running.

<?php
/**
* ACF Rule Match: Page Type
*
* @author Bill Erickson
* @see http://www.billerickson.net/acf-custom-location-rules
*
* @param boolean $match, whether the rule matches (true/false)
* @param array $rule, the current rule you're matching. Includes 'param', 'operator' and 'value' parameters
* @param array $options, data about the current edit screen (post_id, page_template...)
* @return boolean $match
*/
function ea_acf_rule_match_page_type( $match, $rule, $options ) {
// Only run for 'no_children' value
if ( 'no_children' != $rule['value'] ) {
return $match;
}
// Only run if post ID is defined and this is a page
if ( ! $options['post_id'] || 'page' != get_post_type( $options['post_id'] ) ) {
return $match;
}
$children = get_pages( array( 'child_of' => get_the_ID() ) );
if ( '==' == $rule['operator'] ) {
$match = empty( $children );
} elseif ( '!=' == $rule['operator'] ) {
$match = ! empty( $children );
}
return $match;
}
add_filter( 'acf/location/rule_match/page_type', 'ea_acf_rule_match_page_type', 10, 3 );
view raw acf.php hosted with ❤ by GitHub

Share your custom location rules

As you can see, with a few lines of code you can write your own custom logic for when a metabox appears on the page. Be sure to share your own implementations in the comments below.

Bill Erickson

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

About Me
Ready to upgrade your website?

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

Let's Talk

Reader Interactions

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

Comments

  1. Jay says

    Thanks for the post. I’m still new to ACF and WordPress development I’m just finishing up on my second site that isn’t just a blog. ACF has made my life so much easier and this post has made me think a bunch of new possibilities. One of the things I got stuck on was trying to add an ACF field group to a Genesis CPT archive settings page after reading this I should be able to work it out now.

    • Bill Erickson says

      I haven’t tried adding fields to the Genesis CPT Archive page, so I don’t have any code to point you to, but I’m 99% sure ACF wouldn’t work. You would need to use the Genesis filters to add your own metabox to that page, similar to this.

      Alternatively, you can create your own CPT Archive Settings page in ACF. Instead of using acf_add_options_page() to add a top level options page (see here), you can use acf_add_options_sub_page() to add it as a subpage to an existing page, like your CPT. This example adds a “Portfolio Settings” page underneath a CPT called “Projects”.

  2. Jay says

    Thanks. I was just playing with the ACF options pages the other day, I was testing if I could create sub page under Genesis menu option. I didn’t have a use case at the time, just wanted to see if I could. I didn’t think about the possibility of replacing the CPT Archive Settings page I’ll keep that in mind for the next time. I ended up just adding a Theme Customizer setting . I wanted to allow the client to upload their own custom header for a couple of custom post types so realistically the Customizer was probably a better fit for my problem. Sometimes when you have a new hammer everything looks like a nail 🙂

  3. Jehanzeb Anwer says

    Hi Bill,

    I have been always coming on to your website every now and then to learn something new about programming, especially for WordPress as I myself am a freelance wordpress developer. I recently had a project where I used your codes for custom location and it sure helped me a lot as i was pretty much stuck with it. Thanks for sharing this valuable information.

  4. Nik says

    Hi There,

    Is there any guideline to create restaurant directory listing theme using Advance Custom Field ? There I can create Restaurants Listing which include – Restaurant Overview, Menu, Gallery, Opening hours, Address, Phone Number etc…

    Thanks
    Nik

  5. Theresa says

    Hi is there an option to have multiple page location rules? I can’t seem to get it to work. The field group only works for one page. As soon as I add another page to the location rules, the fields won’t display on any page.
    I am thinking there must be a way to have e.g. headings image and title for multiple pages but at the moment I have to create a new field group for each page.

    • Bill Erickson says

      Yes, you should be able to add multiple page location. When adding the location rule on the field group, use the “OR” selector instead of the “AND” selector.