How to set up hierarchical relationships without using plugins / meta query

You should use taxonomies to store your relationships.

Sidenote: A possible answer was to make the releases children of the artists using the classic post_parent field, however a post can only have 1 parent, and the need for a release to also have a director and other relationships eliminates this option.

Taxonomies

You will want to create custom taxonomies for every relationship, focusing on the phrase:

An X can have Y’s

Where X’s are things and Y’s are groups or things you would filter and search for.

So in this case:

  • Releases can have an artist
    • an artist taxonomy
  • releases can have a director
    • a directory taxonomy

You will need to create a basic plugin that registers a taxonomy with these names.

To register a taxonomy, use register_taxonomy:

https://developer.wordpress.org/reference/functions/register_taxonomy/

Most people use a generator to get their initial code, e.g. I go to generatewp and fill out the form to get my starting code for a new taxonomy:

https://generatewp.com/taxonomy/

The important thing here, is that we’re going to use some naming trickery to tie the madonna artist term, to the madonna artist custom post type. So if I have a Lady Gaga artist post, and a release post named The Fame, then I will give “The Fame” the lady gaga artist term, because the Lady Gaga artist post has the slug lady gaga too and they match. When I want the artist I fetch the artist post with the same slug, and when I want the artists releases, I ask for all releases with an artist term of the same name.

The bonus of this is that the release posts can now have multiple artists, e.g. Lady Gagas duet album with Tony Bennet is no longer an issue for your database, and neither are compilation albums.

So let’s create a custom taxonomy and call it something like artists ( make sure it’s not the same as your post type ), then when we create our release we add a term named madonna, making sure it’s identical to the artist posts name/slug.

Then finally, if we are on the madonna artist posts template, and want to list her releases, we might do this:

$releases = new WP_Query( [
    'post_type' => 'release',
    'artists' => 'madonna'
] );
if ( $releases->have_posts() ) {
    while ( $releases->have_posts() ) {
        $releases->the_post();
        the_title(); // prints the release title
        // etc..

Here we’ve asked for all posts of type release that are in the artists taxonomy term named madonna. Of course you will want to fetch the slug of the current artist post so that you don’t have to manually type this out for every single artist.

Automation filters

Of course typing out madonna and every other artist name twice isn’t fun, and what if Madonna changes her name? What if we create a directors taxonomy and they get married or it’s revealed they were actually someone else writing under a false name?

Well we can automate these things using hooks:

  • when an artist post is saved, if the slug was changed, find the term with the same name and change its slug too
    • if no term exists, create it
// when an "artist" post is saved, update the artists term
add_action( 'save_post_artist", 'update_artist_term', 10, 3 );
function update_artist_term( $post_ID, WP_Post $post, bool $update ) : void {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
        return;
    }
    $term = get_term_by( 'slug', $post->post_name, 'artists' );
    if ( empty( $term ) || is_wp_error( $term ) ) {
        // create it!
        wp_insert_term(
            $post->post_title,
            'artists',
            [
                'slug' => $post->post_name
            ]
        );
    }
}

This code is just an example that creates the term but it’s untested and doesn’t update the slug

Templates

Currently you’re using PHP code to fetch and display the releases, but if you used taxonomies, WordPress gives you theme templates and archives for free.

If you register a taxonomy named artists, and used the madonna example, you’d find that a new post archive that shows releases tagged madonna now exists at example.com/artists/madonna, and that a theme template of taxonomy-artists.php will be used if available, or even taxonomy-artists-madonna.php if you felt Madonna needed special attention.

Doing the reverse

If we’re on a release, and we want to list the artists ( the benefit of this approach is a release can have many artists now ), we just need to fetch the names of the terms using wp_get_object_terms:

$artists_terms = wp_get_object_terms( $post_id, 'artists' );

Then we can grab their names:

$artist_slugs = wp_list_pluck( $artists_terms, 'slug' );

And do a query of the artist post type:

$artists = new WP_Query( [
    'post_type' => 'artist',
    'post_name__in' => $artist_slugs,
] );
if ( $artists->have_posts() ) {
    /// etc.. standard post loop that displays each artist like before

The Other Post Types

You can repeat this entire process for every other item. The most difficult part is migrating all that data over which will be frustrating.

The other thing to note:

  • tax_query can let you do more complex queries on taxonomies, but taxonomies are not free, they’re much cheaper and faster than meta_query but they aren’t instant
  • avoid NOT or not_in parameters at all costs, they’re one of the slowest types of query you can do, even slower than meta_query, but not as slow as
  • 'orderby' => 'RAND', will make your database copy the entire table with all your content into memory, randomly order it, then run the original query you wanted. It’s the most expensive, slowest thing you can do, and also the most unnecessary. If you want a random thing just do the random part in PHP. E.g. pick a random date and ask for the first 5 things after that date, much cheaper/faster.

Anything you’re using to filter that’s just a custom field such as release type, or genre, can just be a plain standard custom taxonomy, and this is where you should focus your initial efforts. It’s the easiest way to make the biggest improvement.