How to Query WordPress Multisite by ACF Option Values

Did you know WordPress Multisite got some powerful new functionality in WordPress 5.1, allowing you to query sites by meta values?

This functionality allows you to query sites in a WordPress Multisite network by meta values and other query-based syntax to get back one or more locations.

Like many who develop WordPress websites, I use the awesome Advanced Custom Fields plugin. What ACF Pro gives you is powerful field functionality that really should be core functionality in WordPress itself.

I need to have an options page that allows me to store unique options against a site and then use WP_Site_Query or get_sites() to query for sites with those specific values. One such use case is categorising sites by country.

Surprisingly, there are not a lot of blog posts or even information on using WP_Site_Query or its related functions in WordPress. In my opinion, it’s some of the most useful functionality introduced in WordPress in a very long time. It opens up a lot of possibilities that required hacking together switch_to_blog calls to achieve prior.

Mirror ACF settings to sitemeta

If you have a custom ACF options page registered using acf_add_options_page, it might look something like this:

acf\_add\_options\_page(array(
  'page\_title' 	=> 'Site Settings',
  'menu\_title'	=> 'Site Settings',
  'menu\_slug' 	=> 'site-settings',
  'capability'	=> 'edit\_posts',
  'redirect'		=> false
));

This code will create a custom options page that allows us to add in ACF fields as we would to a custom post type or other screens in WordPress.

Behind the scenes, ACF will use the WordPress options API and numerous methods such as update_option to save the values. Sadly, you cannot query WordPress WP_Site_Query to query for sites by options. However, we can mirror our options to sitemeta that can be queried.

Inside of your functions.php file, add in the following code:

add\_action('acf/save\_post', 'mirror\_options\_to\_sitemeta');
function mirror\_options\_to\_sitemeta($post\_id) {
    $screen = get\_current\_screen();

    if (strpos($screen->id, 'site-settings') == true) {
        //print\_r(get\_site\_meta( get\_current\_blog\_id()));
        $values = get\_fields( $post\_id );

        if ($values) {
            foreach ($values as $name => $value) {
                update\_site\_meta(get\_current\_blog\_id(), $name, $value);
            }
        }
    }

    return true;
}

Using the get_current_screen() method, we determine if the screen is the same as our menu slug from the code above. The acf/save_post action will get fired every time options on our ACF options screen are saved. We do this, so it doesn’t fire for other ACF save calls (acf/save_post is a generic hook called for any ACF save).

Querying WordPress Multisite by meta values

This is the fun part. I have a function called using the WordPress REST API that allows you to query for sites that belong to a specific country.

$site = new WP\_Site\_Query();

sites = $site->query([
  'number' => 99999,
  'meta\_query' => [
    [
      'key'   => 'country',
      'value' => urldecode($request->get\_param('country'))
    ]
  ]
]);

I am setting a stupidly high number because there could be hundreds or thousands of sites that come back. The secret sauce of this query is the meta_query which might look familiar if you have ever written meta queries when querying using WP_Post.

A more contrived example if you were to manually query by meta value would be:

$site = new WP\_Site\_Query();

sites = $site->query([
  'number' => 99999,
  'meta\_query' => [
    [
      'key'   => 'country',
      'value' => 'australia'
    ]
  ]
]);

This will return an array of WP_Site objects. However, the downside is that the WP_Site object returned might not have helpful information, like the site’s name. This is where you’ll need to use the methods get_blog_details and possibly get_site_meta to get the additional data.