It took me a while to really grasp the power of WordPress’ Actions and Filters. Now I love using them and have recently discovered how they can be used to generate the correct Schema data depending on what the WordPress Loop is generating.

The WordPress Loop is what automatically retrieves and displays WordPress content (typically a Post, Page or Custom Post Type). A typical loop would look like this:

<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

  <article <?php post_class(); ?>>
    <?php // Page/Post Contents ?>
  </article>

<?php endwhile; ?>
<?php endif; ?>

Now if we were displaying a Page, we would probably want to use the Article Schema.

<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

  <article <?php post_class(); ?> itemscope itemtype="http://schema.org/Article">
    <?php // Page/Post Contents ?>
  </article>

<?php endwhile; ?>
<?php endif; ?>

But hang on a minute! The Article Schema is great for Pages but what about Blog Posts? Technically they aren’t “Articles” and what’s more there is a Blog Post Schema. In that case the code should look like this:

<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

  <article <?php post_class(); ?> itemscope itemtype="http://schema.org/BlogPosting">
    <?php // Page/Post Contents ?>
  </article>

<?php endwhile; ?>
<?php endif; ?>

This is probably where we’d fall back on using page.php to display WordPress pages and single.php to display WordPress posts since they each require different Schema data. But for the sake of this example, we can use filters to output the appropriate Schema. Here’s our new loop code:

<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

  <article <?php post_class(); ?> itemscope itemtype="<?php echo apply_filters( 'item_schema', 'http://schema.org/CreativeWork' ); ?>">
    <?php // Page/Post Contents ?>
  </article>

<?php endwhile; ?>
<?php endif; ?>

The apply_filters() function accepts 2 arguments. The first argument is the name of the filter (in this case I named it item_schema) and the second is the value to be modified or even replaced completely. I find it useful to use a default value of sorts as the second parameter and in this instance I’ve opted for the Creative Work Schema because, according to Schema.org, it is the most generic kind of creative work. That sounds good enough.

But now we need a bit of code in our functions.php before the whole thing will work:

/**
 * Returns the appropriate Schema for the Creative Work being displayed.
 */
function adjust_item_schema( $item_type ) {
  $new_item_type_attr = $item_type;

  if ( is_page() ) {
    $new_item_type_attr = 'http://schema.org/Article';
  } elseif ( is_single() ) {
    $new_item_type_attr = 'http://schema.org/BlogPosting';
  }

  return $new_item_type_attr;
}

add_filter( 'item_schema', 'adjust_item_schema' );

You can add to the if-else statement to accommodate Custom Post Types. For example if you were displaying a Product you could output the Product Schema.

Now a few of you eagle-eyed WordPress haxx0r5 out there might say “BlogPosting Schemas need to be encapsulated within the Blog Schema!” Right you are! And this is where WordPress actions can come into play. Adjusting our loop code a little bit more:

<?php do_action( 'before_loop' ); ?>

<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

  <article <?php post_class(); ?> itemscope itemtype="<?php echo apply_filters( 'item_schema', 'http://schema.org/CreativeWork' ); ?>">
    <?php // Page/Post Contents ?>
  </article>

<?php endwhile; ?>
<?php endif; ?>

<?php do_action( 'after_loop' ); ?>

And a bit more code in our functions.php:

function adjust_before_loop() {
  if ( is_home() || is_single() ) {
    echo '<div itemscope itemtype="https://schema.org/Blog">';
  }
}

add_action( 'before_loop', 'adjust_before_loop' );


function adjust_after_loop() {
  if ( is_home() || is_single() ) {
    echo '</div>';
  }
}

add_action( 'after_loop', 'adjust_after_loop' );

So if the user is currently viewing either the blog page index or a single post, a <div> wrapper is created with the appropriate schema.