So the problem with this was solved, but it became even trickier. So what I wanted to add was a custom Taxonomy to be displayed for each item, as well as filtering by a custom field value.

So the first problem was getting the custom taxonomy to relate to the custom post type. Much of the documentation online seems to overcomplicate this and a lot of it doesn’t quite work for some reason (possibly my misunderstanding of someone else’s work). So I added this to my functions.php file

function create_offer_tax() {
  register_taxonomy(
    'offer_tax',
    'special_offers',
    array(
      'label' => __( 'Offer Category' ),
      'rewrite' => array( 'slug' => 'offer_tax' ),
      'hierarchical' => true,
      'update_count_callback' => '_update_post_term_count',
    )
  );
}

And I added this to my custom post array, also in the functions file

'taxonomies' => array('offer_tax'),

Next up, how to call it in to the page… another tricky one, and using get_categories doesn’t work, because it’s a custom Taxonomy. So within my get_posts I had to define another variable, which is called from the custom taxonomy, and for each one echo out name within an li, all wrapped in a ul, which looks like this:

$offer_terms = wp_get_object_terms( $offer->ID,  'offer_tax' );
  if ( ! empty( $offer_terms ) ) {
    if ( ! is_wp_error( $offer_terms ) ) {
      echo '<ul class="offer_categories">';
        foreach( $offer_terms as $term ) {
          echo '<li>' . esc_html( $term->name ) . '</li>'; 
        }
    echo '</ul>';
  }
}

And that all sits within the foreach of the get_posts.

I also had to update my get_posts array to check whether two demands are met – firstly is the custom field ‘applies_to’ the same as the current page ID, and is the custom field ‘featured’ false, then display the relevant posts.

So that looks like this:

$special_offers = get_posts(array(
  'numberposts' => -1,
  'post_type' => 'special_offers',
  'post_status'      => 'publish',
  'meta_query' => array(
    'relation' => 'AND',
    array(
      'key' => 'applies_to', // name of custom field
      'value' => '"' . get_the_ID() . '"',
      'compare' => 'LIKE'
    ),
    array(
      'key' => 'featured',
      'value' => '0',
      'compare' => 'LIKE'
    )
  )
));

So my final code looks like this:

<?php
$special_offers = get_posts(array(
	'numberposts' => -1,
    'post_type' => 'special_offers',
    'post_status'      => 'publish',
    'meta_query' => array(
    	'relation' => 'AND',
        array(
            'key' => 'applies_to', // name of custom field
            'value' => '"' . get_the_ID() . '"',
            'compare' => 'LIKE'
        ),
		array(
			'key' => 'featured',
			'value' => '0',
			'compare' => 'LIKE'
		)
    )
));

if( $special_offers ):

	echo "<h3 class='special_offer_title'>Special Offers</h3>";

    foreach( $special_offers as $offer ): 
    // Do something to display the articles. Each article is a WP_Post object.

	echo "<div class='special_offer'><h3>";
    echo $offer->post_title;  // The post title
    echo "</h3>";

	$offer_terms = wp_get_object_terms( $offer->ID,  'offer_tax' );
	if ( ! empty( $offer_terms ) ) {
		if ( ! is_wp_error( $offer_terms ) ) {
			echo '<ul class="offer_categories">';
				foreach( $offer_terms as $term ) {
					echo '<li>' . esc_html( $term->name ) . '</li>'; 
				}
			echo '</ul>';
		}
	}

    echo "<span class='offer_text'>";
	echo $offer->post_content;  // The post content
	echo "</span></div>";

    endforeach;
endif;
?>