In this post, we’ll learn how to build a custom page to display posts of a specific ‘Category’. Having a category based page is a handy way to direct your visitors to a specific set of your posts without the risk of distracting them with all your other great content.
The steps will be to:
- create a custom page template
- understanding the ‘Loop’
- understand how WP_Query works in this use case
- modify the php code to pull (or filter) just the posts having the category we want
- display the posts – in this case, posts with my WordCamp presentations
- return control to the normal page ‘Loop’ so that the rest of the content is displayed properly
Creating a Custom Page Template
First go to your cPanel, open your child theme, create a file named page_category.php. You can put this file in the top level of your child theme folder or, if you expect to create several custom page templates, you might decide to organize them in a separate custom template folder. In this example, I have the new file ‘page_category.php’ at the top level of my child theme.

Now open the page_category.php file, delete the header and put the following code at the top of the file (just above the ‘get_header’ statement).
<?php /* Template Name: Display Category */?>
Here’s what the top of the page_category.php looks like:

Now create a new page and confirm that the newly created template is available. Here, I’m using the Gutenberg Block Editor and ‘page attributes is shown in the right hand pane.

Understanding the ‘Loop’
Here’s where we introduce WP_Query and the concept of the Loop.
You can learn more about this from the WordPress Codex at https://developer.wordpress.org/reference/classes/wp_query/ (here’s an excerpt)
…during The Loop. WP_Query
provides numerous functions for common tasks within The Loop. To begin with, have_posts(), which calls $wp_query->have_posts()
, is called to see if there are any posts to show. If there are, a while
loop is begun, using have_posts() as the condition. This will iterate around as long as there are posts to show. In each iteration, the_post(), which calls $wp_query->the_post()
is called, setting up internal variables within $wp_query
and the global $post
variable …

Well……
It’s really not all that bad!
The ‘Loop’ is really a while loop that searches the WordPress database of the website to find all the posts, then grabs the components of each post, one at a time, (components include; title, content, post meta data: author, date etc). Then calls template parts to display each post, then goes to the next post until there are no more posts to display.
Let’s compare the Loop for the twentyseventeen (default) and oceanwp themes. This website is built on oceanwp. You will notice that both themes contain the same have_post() and the_post() statements but handle template parts and comments differently. Here we’ll concentrate on the post functions.
while ( have_posts() ) :
the_post();
From the Twenty Seventeen default theme
<?php // Elementor `single` location if ( ! function_exists( 'elementor_theme_do_location' ) || ! elementor_theme_do_location( 'single' ) ) { // Start loop while ( have_posts() ) : the_post(); get_template_part( 'partials/page/layout' ); endwhile; } ?>
From the OceanWP custom theme
<?php while ( have_posts() ) : the_post(); get_template_part( 'template-parts/page/content', 'page' ); // If comments are open or we have at least one comment, load up the comment template. if ( comments_open() || get_comments_number() ) : comments_template(); endif; endwhile; // End the loop. ?>
What is WP_Query?
WP_Query is a class defined in WordPress. It allows developers to write custom queries and display posts using different parameters.
Simply put, WP_Query acts like a database language (think SQL or MySQL). You can think of WP_Query like a ‘filter’ in Excel which will allow you to see only those rows which meet a specific criteria (example: filter a customer table for customers first name = John).
How to use WP_Query
The following code is courtesy of Artisan Web which I modified slightly for this example.
NOTE: WordPress is licensed as open source under GNU license. This means that you can and should, use, modify, and expand on other peoples code. This is perfectly legal and is encouraged!
<?php /* Template Name: Category */ ?> <?php /* ******************************** /* This is the WP_Query code */ $args = array( 'post_type' => 'post', 'post_status' => 'publish', 'category_name' => 'wordcamp', 'posts_per_page' => 5, ); $arr_posts = new WP_Query( $args ); /* ******************************** /* The Loop and display code */ if ( $arr_posts->have_posts() ) : while ( $arr_posts->have_posts() ) : $arr_posts->the_post(); ?> <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>> <?php if ( has_post_thumbnail() ) : the_post_thumbnail(); endif; ?> <header class="entry-header"> <h1 class="entry-title"><?php the_title(); ?></h1> </header> <div class="entry-content"> <?php the_excerpt(); ?> <a href="<?php the_permalink(); ?>">Read More</a> </div> </article> <?php endwhile; endif; ?>
While this may look complicated, most of the code is used to display the post excerpts as ‘articles’. The top block is the WP_Query ‘filter’.
/* ****
/* This is the WP_Query code */
$args = array(
‘post_type’ => ‘post’,
‘post_status’ => ‘publish’,
‘category_name’ => ‘wordcamp’,
‘posts_per_page’ => 5,
);
$arr_posts = new WP_Query( $args );
First, the variable ‘$args’ is defined as an array which defines the parameters of the database query as; published posts whose category is ‘wordamp’. Then we’ll set a limit of 5 posts to be displayed per page.
Finally, we’ll do a ‘new WP_query’ using the parameters in $args and put the post data for each post found into the array named ‘$arr_posts’
That’s it!
Now let’s see if it worked…

We’ll we can see this code worked because it displays excerpts of both posts whose category is ‘WordCamp’. But it has no styling. No header, footer, navbar, or sidebar.
More work needs to be done.
Adding essential page components
Open the page.php file from the parent theme. Copy and paste the commands get_header(), and get_footer() and place them in the appropriate places near the top and bottom of the page code. Test. Add in the theme’s (oceanwp) ocean_before and ocean_after sections. Heree’s the completed custom code for the category page template.
<?php /* Template Name: Category */ ?> <?php get_header(); ?> <?php // ****************** copy before objects from parent theme page.php ******** ?> <?php do_action( 'ocean_before_content_wrap' ); ?> <div id="content-wrap" class="container clr"> <?php do_action( 'ocean_before_primary' ); ?> <div id="primary" class="content-area clr"> <?php do_action( 'ocean_before_content' ); ?> <div id="content" class="site-content clr"> <?php do_action( 'ocean_before_content_inner' ); ?> <?php // **************** replace the normal while loop with WP_Query **************** $args = array( 'post_type' => 'post', 'post_status' => 'publish', 'category_name' => 'wordcamp', 'posts_per_page' => 5, ); $arr_posts = new WP_Query( $args ); if ( $arr_posts->have_posts() ) : while ( $arr_posts->have_posts() ) : $arr_posts->the_post(); ?> <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>> <?php if ( has_post_thumbnail() ) : the_post_thumbnail(); endif; ?> <header class="entry-header"> <h1 class="entry-title"><?php the_title(); ?></h1> </header> <div class="entry-content"> <?php the_excerpt(); ?> <a href="<?php the_permalink(); ?>">Read More</a> </div> </article> <?php endwhile; endif; ?> <?php // ***************** add the after section ******************** ?> <?php do_action( 'ocean_after_content_inner' ); ?> </div><!-- #content --> <?php do_action( 'ocean_after_content' ); ?> </div><!-- #primary --> <?php do_action( 'ocean_after_primary' ); ?> <?php do_action( 'ocean_display_sidebar' ); ?> </div><!-- #content-wrap --> <?php do_action( 'ocean_after_content_wrap' ); ?> <?php get_footer(); ?>

Now we need to return control to the normal page loop so I added this just after the endif statement:
endif;
// add return control to normal page loop after this WP_Query
wp_reset_postdata();
?>
This is a good practice even though in this case it didn’t make any difference in how the page displays.