Creating a Post Series Plugin for WordPress

Share this article

A WordPress Post Series plugin enables you to organize your posts serially to create a book or a course. It provides users a path for learning. Posts series plugins can also be used to split a long post into multiple parts.

In this tutorial, I’ll show you how to create a plugin for displaying a series of posts. You can also integrate the same code into a theme, as theme functionality.

Taxonomies Versus Post Series

In WordPress, taxonomies are used to group or organize similar posts together. But WordPress doesn’t provide a way to display all the posts of a particular taxonomy in a customized, serial manner. WordPress taxonomies are displayed using an archive.php file, so we cannot create a post series as a single, indexable post.

So we need a post series, which is actually one post that contains other posts in a serial manner.

How to Create a Post Series

There are many different ways to create a post series. Popular post series plugins found at WordPress.org use custom taxonomies on WordPress posts to create a post series, but in this tutorial I’ll use Custom Post Types instead.

Plugin File Structure

Create a plugin directory named sitepoint-post-series and place two files in this, named sitepoint-post-series.php and sitepoint-post-series.css.

In the sitepoint-post-series.php file, place the code below, so that WordPress recognizes the directory as a plugin and lets you install it.

<?php

/*
Plugin Name: SitePoint Post Series
Plugin URI: https://www.sitepoint.com/
Description: This used is used to create a post series.
Version: 1.0
Author: Narayan Prusty
*/

You can also add post series functionality to a theme. In this case, you will need to place all the code referred to in this tutorial, in the theme’s functions.php file.

How to Create a Post Series Custom Post Type

First, we need to create a custom post type, where each custom post type represents a post series.

Place the code below in a file called sitepoint-post-series.php:

function sitepoint_post_series_custom_post_type()
{
	register_post_type("sitepoint-postseries", array(
			"labels" => array("name" => __("Post Series"), "singular_name" => __("Post Series")),
			"public" => true, 
			"has_archive" => true,
			"rewrite" => array("slug"=> "post-series"),
			"supports" => array("editor", "title", "excerpt", "thumbnail", "comments"),
			"capability_type" => "post",
			"publicly_queryable" => true,
			"taxonomies" => array("category", "post_tag"),
		)
	);
}	

add_action("init", "sitepoint_post_series_custom_post_type", 2);

/* Flush Rewrite Rules */

function sitepoint_post_series_activation()
{
	sitepoint_post_series_custom_post_type();
	flush_rewrite_rules();
}

register_activation_hook( __FILE__, "sitepoint_post_series_activation");
register_deactivation_hook( __FILE__, "sitepoint_post_series_activation");

Here, we created a custom post type with the same taxonomies that are used by WordPress posts. This is so that you can create a category post series too.

We also added activation and deactivation hooks to flush rewrite rules. This is so that the post series can be viewed on the front end.

Here is what our custom post type looks on the admin screen:

Post Series

Adding a Post Series Meta Box to Posts

Now we need to add meta boxes to the WordPress Posts admin interface. This is so that authors can attach a post to a post series, and provide a serial number to sort the posts inside a post series.

Here is the code to add a meta box to post series:

/* Add Custom Meta Boxes in WordPress Posts */

function sitepoint_post_series_meta_box_markup($object)
{
	wp_nonce_field(basename(__FILE__), "sitepoint-postseries");

	?>
		<div>
			<label for="sitepoint-postseries-serial-number">Serial Number</label>
            <br>
            <input name="sitepoint-postseries-serial-number" type="text" value="<?php echo get_post_meta($object->ID, "sitepoint-postseries-serial-number", true); ?>">

            <br>

            <label for="sitepoint-postseries-id">Name</label>
            <br>
            <select name="sitepoint-postseries-id">
            	<option value="">-</option>
            	<?php
            		$posts = get_posts("post_type=sitepoint-postseries");
            		$selected_series = get_post_meta($object->ID, "sitepoint-postseries-id", true);
            		foreach($posts as $post) 
            		{
            			$id_post = $post->ID; 
            			if($id_post == $selected_series)
            			{
	            			?>
	            				<option selected value="<?php echo $post->ID; ?>"><?php echo $post->post_title; ?></option>	
	            			<?php
            			}
            			else
            			{
	            			?>
	            				<option value="<?php echo $post->ID; ?>"><?php echo $post->post_title; ?></option>	
	            			<?php	
            			}
		            }
            	?>	
            </select>
        </div>
	<?php
}

function sitepoint_post_series_custom_meta_box()
{
	add_meta_box("sitepoint-postseries", "Post Series", "sitepoint_post_series_meta_box_markup", "post", "side", "low", null);
}

add_action("add_meta_boxes", "sitepoint_post_series_custom_meta_box");

Here we add two fields to the meta box. The text field is used by the author to enter the serial number, and the drop down is used to select the post series name to which the post belongs to. If you don’t want to add a post to a post series, then either one or both fields should be left blank.

Here is how it looks on the admin post screen:

WordPress Post Series

Now we need to save the meta box fields when the form is saved. Here is the code to do that:

/* Callback to Save Meta Data */

function sitepoint_post_series_save_custom_meta_box($post_id, $post, $update)
{

	if(!isset($_POST["sitepoint-postseries"]) || !wp_verify_nonce($_POST["sitepoint-postseries"], basename(__FILE__)))
		return $post_id;

	if(!current_user_can("edit_post", $post_id))
		return $post_id;

	if(defined("DOING_AUTOSAVE") && DOING_AUTOSAVE)
		return $post_id;

	$slug = "post";
	if($slug != $post->post_type)
        return;

	$serial_number = null;
	if(isset($_POST["sitepoint-postseries-serial-number"]))
    {
        $serial_number = $_POST["sitepoint-postseries-serial-number"];
    }
    else
    {
    	$serial_number = "";
    }
    update_post_meta($post_id, "sitepoint-postseries-serial-number", $serial_number);

	$series_id = null;
	if(isset($_POST["sitepoint-postseries-id"]))
    {
        $series_id = $_POST["sitepoint-postseries-id"];
    }
    else
    {
    	$series_id = "";
    }

    $previous_series_id = get_post_meta($post_id, "sitepoint-postseries-id", true);

    update_post_meta($post_id, "sitepoint-postseries-id", $series_id);

    //no series, removing series, adding new series or changing series

    if($previous_series_id == "" && $series_id == "")
    {
    	sitepoint_post_series_save_settings($series_id, $serial_number, $post_id);
    }
    else if($previous_series_id != "" && $series_id == "")
    {
    	sitepoint_post_series_save_settings($previous_series_id, "", $post_id);	
    }
    else if($previous_series_id == "" && $series_id != "")
    {
    	sitepoint_post_series_save_settings($series_id, $serial_number, $post_id);
    }
    else if($previous_series_id != "" && $series_id != "")
    {
    	sitepoint_post_series_save_settings($previous_series_id, "", $post_id);
    	sitepoint_post_series_save_settings($series_id, $serial_number, $post_id);	
    }    
}

add_action("save_post", "sitepoint_post_series_save_custom_meta_box", 10, 3);

Here we are saving the meta box content and then calling the function sitepoint_post_series_save_settings with different argument values depending on whether the user is removing a series, adding a series or changing a series.

Here is the code for the sitepoint_post_series_save_settings function

/* Store WordPress posts and Post Series CTY relations as WordPress Settings. */

function sitepoint_post_series_save_settings($series_id, $serial_number, $post_id)
{
    if($series_id != "" && $serial_number != "")
    {
	    $post_series_list = get_option("post_series_" . $series_id . "_ids", "");

	    if($post_series_list == "")
	    {
	    	$post_series_list_array = array($post_id);
	    	$post_series_list = implode (", ", $post_series_list_array);

	    	update_option("post_series_" . $series_id . "_ids", $post_series_list);
	    }
	    else
	    {
	    	$post_series_list_array = explode(',', $post_series_list);

	    	if(in_array($post_id, $post_series_list_array))
	    	{
	    		//do nothing
	    	}
	    	else
	    	{
	    		$post_series_list_array[] = $post_id;
	    		$post_series_list = implode (", ", $post_series_list_array);
	    		update_option("post_series_" . $series_id . "_ids", $post_series_list);
	    	}
	    }
    }
    else if($series_id == "" || $serial_number == "")
    {
    	$post_series_list = get_option("post_series_" . $series_id . "_ids", "");

    	if($post_series_list == "")
    	{
    	}
    	else
    	{
    		$post_series_list_array = explode(',', $post_series_list);

    		if(in_array($post_id, $post_series_list_array))
    		{
    			//here remove the post id from array.
    			if(($key = array_search($post_id, $post_series_list_array)) !== false) {
				    unset($post_series_list_array[$key]);
				}
    			$post_series_list = implode (", ", $post_series_list_array);
	    		update_option("post_series_" . $series_id . "_ids", $post_series_list);
	    	}
    		else
    		{
    		}
    	}
    }
}

This function creates a string, which stores the WordPress post ID’s that belong to a particular series. And then it stores the strings as a WordPress setting.

Now we’re done with all the admin area code. You should now be able to create posts and assign them to a series. And also assign categories and tags to each series.

Now let’s code the front end to display the post series.

Making Post Series Visible on the Index Page and Archive Pages

The custom post type is not yet visible in the index and archive pages. To make it visible on these pages as well, you just need to add the code below:

/* Displaying Custom Post Types on Index Page */

function sitepoint_post_series_pre_posts($q)
{
	if(is_admin() || !$q->is_main_query() || is_page())
        return;

    $q->set("post_type", array("post", "sitepoint-postseries"));
}

add_action("pre_get_posts", "sitepoint_post_series_pre_posts");

Here we’re using pre_get_posts hook to add a post series to the $q variable, which is used by the main loop to displays posts.

Displaying Posts of a Post Series

We need to filter the content of the post series type and add posts belonging to the series.

Here is the code to add posts of a post series in a post series page.

function sitepoint_post_series_content_filter($content)
{	
	$slug = "sitepoint-postseries";
	if($slug != get_post_type())
        return $content;

	$post_series_list = get_option("post_series_" . get_the_ID() . "_ids", "");
	$post_series_list_array = explode(',', $post_series_list);

	$post_series_serial_number = array();

	foreach($post_series_list_array as $key => $value)
	{
		$serial_number = get_post_meta($value, "sitepoint-postseries-serial-number", true);
		$post_series_serial_number[$value] = $serial_number;
	}

	asort($post_series_serial_number);

	$html = "<ul class='sitepoint-post-series'>";

	foreach($post_series_serial_number as $key => $value) 
	{

		$post = get_post($key);
		
		$title = $post->post_title;
		
		$excerpt = $post->post_content;
		$shortcode_pattern = get_shortcode_regex();
        $excerpt = preg_replace('/' . $shortcode_pattern . '/', '', $excerpt);
        $excerpt = strip_tags($excerpt); 
        $excerpt = esc_attr(substr($excerpt, 0, 150));

        $img = "";

        if(has_post_thumbnail($key))
        {
        	$temp = wp_get_attachment_image_src(get_post_thumbnail_id($key), array(150, 150));
        	$img = $temp[0];
        }
        else
        {
        	$img = "https://lorempixel.com/150/150/abstract";
        }

        $html = $html . "<li><h3><a href='" . get_permalink($key) . "'>" . $title . "</a></h3><div><div class='sitepoint-post-series-box1'><img src='" . $img . "' /></div><div class='sitepoint-post-series-box2'><p>" . $excerpt . " ...</p></div></div><div class='clear'></div></li>";
	} 

	$html = $html . "</ul>";

	return $content . $html;
}

add_filter("the_content", "sitepoint_post_series_content_filter");

This displays the posts using HTML unordered list tag. For posts without an image we are loading a image from Lorempixel cloud service to generate random texture images.

We are retrieving the posts of a post series from the setting string, which we saved during the saving of meta data.

Adding Post Series Information to Posts

We can also add a post series box on posts that belong to a post series to indicate to the user that the post belongs to a specific posts series. Here’s the code to do that:

/* Adding Content to WordPress Posts which belong to a Series */

function sitepoint_post_series_post_content_filter($content)
{	
	$slug = "post";
	if($slug != get_post_type())
        return $content;

	$serial_number = get_post_meta(get_the_ID(), "sitepoint-postseries-serial-number", true);    
	$series_id = get_post_meta(get_the_ID(), "sitepoint-postseries-id", true);

    if(get_post_status($series_id) == "publish")
    {
        $html = "";
        
        if($series_id != "" || $serial_number != "")
        {
            $html = "<div class='sitepoint-post-series-post-content'><div>This post is a part " . $serial_number . " of <a href='" . get_permalink($series_id) . "'>" . get_the_title($series_id) . "</a> post series.</div></div>";    
        }

        $content = $html . $content; 
    }

    return $content;
}

Here we are just displaying a post series name and which part of this post is from the series.

You can also add the next and previous post of the series by using the below implementation of the sitepoint_post_series_post_content_filter function:

function sitepoint_post_series_post_content_filter($content)
{	
	$slug = "post";
	if($slug != get_post_type())
        return $content;

	$serial_number = get_post_meta(get_the_ID(), "sitepoint-postseries-serial-number", true);    
	$series_id = get_post_meta(get_the_ID(), "sitepoint-postseries-id", true);

	if($serial_number != "" && $series_id != "")
	{
		//find next and previous post too.

		$post_series_list = get_option("post_series_" . $series_id . "_ids", "");
		$post_series_list_array = explode(',', $post_series_list);

		$post_series_serial_number = array();

		foreach($post_series_list_array as $key => $value)
		{
			$serial_number = get_post_meta($value, "sitepoint-postseries-serial-number", true);
			$post_series_serial_number[$value] = $serial_number;
		}

		asort($post_series_serial_number);

		$post_series_serial_number_reverse = array();

		$iii = 1;

		foreach($post_series_serial_number as $key => $value) 
		{
			$post_series_serial_number_reverse[$iii] = $key;
			$iii++;
		}

		$index = array_search(get_the_ID(), $post_series_serial_number_reverse);

		if($index == 1)
		{
			$html = "<div class='sitepoint-post-series-post-content'><div>This post is a part of <a href='" . get_permalink($series_id) . "'>" . get_the_title($series_id) . "</a> post series.</div><div>&#9112; Next: <a href='" . get_permalink($post_series_serial_number_reverse[$index + 1]) . "'>" . get_the_title($post_series_serial_number_reverse[$index + 1]) . "</a></div></div>";
			$content = $html . $content;
		}
		else if($index > 1 && $index < sizeof($post_series_serial_number_reverse))
		{
			$html = "<div class='sitepoint-post-series-post-content'><div>This post is a part of <a href='" . get_permalink($series_id) . "'>" . get_the_title($series_id) . "</a> post series.</div><div>&#9112; Next post in the series is <a href='" . get_permalink($post_series_serial_number_reverse[$index + 1]) . "'>" . get_the_title($post_series_serial_number_reverse[$index + 1]) . "</a></div><div>&#9111; Previous post in the series is <a href='" . get_permalink($post_series_serial_number_reverse[$index - 1]) . "'>" . get_the_title($post_series_serial_number_reverse[$index - 1]) . "</a></div></div>";
			$content = $html . $content;
		}
		else if($index == sizeof($post_series_serial_number_reverse))
		{
			$html = "<div class='sitepoint-post-series-post-content'><div>This post is a part of <a href='" . get_permalink($series_id) . "'>" . get_the_title($series_id) . "</a> post series.</div><div>&#9111; Previous: <a href='" . get_permalink($post_series_serial_number_reverse[$index - 1]) . "'>" . get_the_title($post_series_serial_number_reverse[$index - 1]) . "</a></div></div>";
			$content = $html . $content;
		}
	}

	return $content;

}

The problem with this implementation is that the code hits the MySQL number of times equal to the number of posts in the series. There is a performance issue if you have a lot of posts for a particular post series, but I’ve included it for educational purposes.

Comparing Our Plugin to Other Post Series Plugins

Here are some of the plugins on WordPress.org that enable you to create a post series. I have compared each with the plugin we’ve created above.

  1. Organize Series: Organize Series adds a custom taxonomy to WordPress posts. When you view the custom taxonomy it adds a heading to the archive page, which seems like the title of the post series. And posts in a post series are displayed like taxonomies, which may not be great from a user perspective. In comparison, our plugin uses custom post types to display a single post series so that you can add featured images, text or markup content (and more) to a post series. The posts of a post series look different compared to the archive page in our plugin.
  2. WP Post Series: This also behaves like the Organize Series plugin. One difference is that it doesn’t provide any customization to the post series page.

Our plugin is highly customizable and doesn’t include any WordPress hacks. As such, it is much more compatible.

Conclusion

If you own a development blog, then you could use this plugin to create your own post series, which can increase your engagement and conversion rates. You can even use it to split up your large posts into multiple posts.

Feel free to comment on your experiences with the plugin below.

Frequently Asked Questions about Creating a Post Series Plugin for WordPress

What is a post series plugin and why do I need it for my WordPress site?

A post series plugin is a tool that allows you to group related posts together into a series. This is particularly useful for blogs or websites that publish content in a serialized format. It enhances the user experience by making it easier for readers to navigate through related content. It also helps in improving the SEO of your website as search engines favor well-structured and interlinked content.

How does a post series plugin differ from the standard WordPress categories and tags?

While WordPress categories and tags do allow you to group related posts together, they lack the sequential structure that a post series plugin provides. With a post series plugin, you can order your posts in a specific sequence, making it easier for readers to follow a storyline or a structured learning path.

Can I customize the look and feel of the post series on my website?

Yes, most post series plugins offer customization options. You can change the layout, colors, fonts, and other design elements to match the overall aesthetic of your website. Some plugins also allow you to add custom CSS for more advanced customization.

How does creating a post series impact my website’s SEO?

Creating a post series can significantly improve your website’s SEO. By grouping related posts together, you’re creating a rich network of internal links that search engines love. It also increases the dwell time on your website as readers are more likely to read multiple posts in a series.

Can I create multiple post series on my WordPress site?

Absolutely! You can create as many post series as you need. This is particularly useful for websites that cover multiple topics or have different types of serialized content.

Is it possible to add posts to a series after it has been published?

Yes, you can add new posts to a series at any time. Most post series plugins allow you to easily manage and update your series, ensuring that your content remains relevant and up-to-date.

Can I use a post series plugin on a multi-author WordPress site?

Yes, a post series plugin can be used on a multi-author site. It can be a great tool for managing and organizing content from multiple authors, ensuring consistency and coherence across all posts.

Do I need coding skills to create a post series on WordPress?

No, you don’t need any coding skills to create a post series on WordPress. Most post series plugins come with a user-friendly interface that makes it easy to create and manage your series.

Can I use a post series plugin with my existing WordPress theme?

Yes, most post series plugins are designed to work seamlessly with any WordPress theme. However, it’s always a good idea to check the plugin’s documentation or contact the developer for confirmation.

Are there any free post series plugins available for WordPress?

Yes, there are several free post series plugins available for WordPress. However, keep in mind that free plugins may not offer as many features or as much support as premium plugins. Always choose a plugin that best fits your needs and budget.

Narayan PrustyNarayan Prusty
View Author

Narayan is a web astronaut. He is the founder of QNimate. He loves teaching. He loves to share ideas. When not coding he enjoys playing football. You will often find him at QScutter classes.

ChrisBpluginplugin developmentpluginspost seriesWordPress
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week