Retrieve taxonomies from arbitrary site

how can I retrieve all taxonomy terms for a post in an arbitrary site programmatically? In other words, given post $post and site $siteId, how can I retrieve all terms for all taxonomies of that post in that site?

You can do this by omitting the taxonomy from WP_Term_Query:

$query = new WP_Term_Query( [
    'object_ids' => [ $post_id ]
] );
$terms = $query->get_terms();

Note that this gives you no information that the taxonomys of those terms were registered with, so permalinks and labels may not be available

How can I retrieve all taxonomies of an arbitrary site? In other words, given that I have a site $siteId, how can I retrieve all taxonomies on that site?

Unfortunately, no, there is no API or function call in PHP that will give you this information.

Generally needing this information is a sign that something has gone wrong at the high level architecture, and that simpler easier solutions exist. Every decision has trade offs and built in constraints, and this is one of those situations where breaching a constraint requires a tradeoff or an architectural change.

The root problem is that switch_to_blog changes the tables and globals, but it doesn’t load any code from the other blog being switched to, so we need to recover what the registered posts types and taxonomies are from that site.

Work Arounds

However, there are work arounds, which one is best for you will depend on several things, and each has their own advantages and disadvantages.

  1. Retrieving all terms and inspecting their taxonomy field
  2. WP CLI
  3. REST API
  4. Dedicated REST API endpoints
  5. Pushing Data
  6. Uniform taxonomies, but with different visibility
  7. Preparing data in advance

These are all work arounds that try to cater for the edge case you’ve asked about.

Of all of these, only WP CLI reliably gives all the needed information without performance and scaling issues. Whichever method is used, cache the result.

Retrieving all Terms

We can retrieve all terms in a site, then loop over them to identify the taxonomy fields:

$query = new WP_Term_Query([]);
$terms = $query->get_terms();
$taxonomies = wp_list_pluck( $terms, 'taxonomy' );
$taxonomies = array_unique( $taxonomies );

However, there are some major problems with this:

  • it does not scale, pulling all terms into memory takes time and memory, eventually the site will have more than can be held leading either to hitting the execution time limit, or memory exhaustion
  • it only shows used taxonomies, if there are no categories then the category taxonomy will not show
  • this gives you no information that the taxonomy of those terms were registered with, so permalinks and labels may not be available

WP CLI

WP CLI can give you exactly what you need for both of your questions, if it’s available. By assembling a command and calling it from PHP, you can programmatically retrieve the data you wish while avoiding a HTTP request.

  • we call WP CLI via either exec or proc_open
  • we specify which site we want via the --url parameter, we can get the URL via a standard API call such as get_site_url( $site_id )
  • We add the --format parameter to ensure WP CLI gives us machine readable results, either --format="csv" for a CSV string that functions such as fgetcsv etc can process, or --format="json" which json_decode can process. JSON will be easier
  • we parse the result in PHP

For example, here is the local copy of my site:

vagrant@vvv:/srv/www/tomjn/public_html$ wp taxonomy list
+----------------+------------------+-------------+---------------+---------------+--------------+--------+
| name           | label            | description | object_type   | show_tagcloud | hierarchical | public |
+----------------+------------------+-------------+---------------+---------------+--------------+--------+
| category       | Categories       |             | post          | 1             | 1            | 1      |
| post_tag       | Tags             |             | post          | 1             |              | 1      |
| nav_menu       | Navigation Menus |             | nav_menu_item |               |              |        |
| link_category  | Link Categories  |             | link          | 1             |              |        |
| post_format    | Formats          |             | post          |               |              | 1      |
| technology     | Technologies     |             | project       | 1             |              | 1      |
| tomjn_talk_tag | Talk Tags        |             | tomjn_talks   | 1             |              | 1      |
| series         | Series           |             | post          |               |              | 1      |
+----------------+------------------+-------------+---------------+---------------+--------------+--------+

I can also pass --format=json or --format=csv to get a machine parseable result.

I can target individual sites in a multisite install by passing the --url parameter

e.g.

wp taxonomy list --url="https://example.com/" --format="json"

I can then call this from PHP and parse the result to get a list of taxonomies.

The same is true of terms, e.g. listing

❯ wp term list category
+---------+------------------+-----------------+-----------------------+-------------+--------+-------+
| term_id | term_taxonomy_id | name            | slug                  | description | parent | count |
+---------+------------------+-----------------+-----------------------+-------------+--------+-------+
| 129     | 136              | Auto-Aggregated | auto-aggregated       |             | 0      | 1     |
| 245     | 276              | Big WP          | big-wp                |             | 4      | 0     |
| 95      | 100              | CSS             | css                   |             | 7      | 1     |
| 7       | 7                | Design          | design                |             | 0      | 3     |
| 30      | 32               | Development     | development           |             | 17     | 31    |
...

Or a posts categories:

❯ wp post term list 15 category --format=csv
term_id,name,slug,taxonomy
5,WordCamp,wordcamp,category

Be sure to set the right working path, and the wp is installed available and executable.

Also be careful, if you ask WP CLI for every post in the database, it will give you it, even if it takes 1 hour to run. Your request will have ran out of time long before then leading to an error. So don’t always provide an upper bounds on how many posts you want, even if you don’t expect to reach it. It’s also possible to request more data than the server has memory to hold, WP CLI will crash with an out of memory error in those situations. E.g. importing a 5GB wxr import file, or requesting 10k posts.

However, if WP CLI isn’t available…

REST API

You can query for taxonomies with the REST API. Every site supports it, but there are 2 caveats:

  • HTTP requests are expensive, and you can’t bypass this cost with a PHP function as you need to load WP from scratch to get the registered taxonomies and post types you desired
  • Private and hidden taxonomies won’t be shown, some taxonomies will only show with authentication, requiring you to add an authentication plugin

But if that’s okay with you, you can use the built in discovery to discover post types and taxonomies.

Visit example.com/wp-json/wp/v2/taxonomies and you’ll get a JSON list of public taxonomies, each object in the list has a types subfield that lists the post types it applies to.

You can also fetch a post this way via the /wp/v2/posts endpoint, but you’ll get term IDs rather than term names back, likely requiring additional queries.

Custom Endpoint

You can try to sidestep the restrictions above by building a custom endpoint. This would allow you to return everything you needed using a single request.

To do this, call register_rest_route to register an endpoint, and return an array of keys and values from the callback. The API will encode these as JSON

Pushing The Data

Rather than retrieving this information from other sites in a multisite installation, it’s much more efficient to have those sites push the information to you then caching the result. This is the most performant method.

Uniform taxonomies, but with different visibility

Some sites can take advantage of this particular possibility. If all sites have the same taxonomies registered then you can query all terms by switching blogs regardless of sites. But nobody said the taxonomies need to have the same options

For example, site A has a category taxonomy, and site B has a tags taxonomy. A does not use tags, and b does not use categories. So we register both taxonomies on both sites, but on A we make tags a hidden private taxonomy, and on B we make categories a hidden private taxonomy.

This work around won’t be suitable for all situations though, and can’t account for taxonomies registered by 3rd party plugins

Preparing data in advance

We don’t know the other sites registered taxonomies and terms for each post because we didn’t load that sites code. However, that site does. So why not make the site store this information somewhere it can be retrieved?

For example, a site could store an option containing the registered taxonomies and their attributes.

The same could be done for a posts terms, a post meta field can be updated on the save hook to contain a list of terms with their names, URLs, and the taxonomy they belong to, so that a term list can be recreated elsewhere without needing to know the registered taxonomies.