Complete developer guide: WP_Query, get_posts, shortcodes. Learn to filter posts by category and optimize queries.
EN

Extracting post lists from categories IN WordPress – Developer guide

5.00 /5 - (34 votes )
Last verified: March 1, 2026
Experience: 5+ years experience
Table of Contents

Introduction to Category-Based Post Retrieval

One of the most common tasks in WordPress development is retrieving posts from specific categories. Whether you’re building a custom homepage layout, creating a category archive template, or displaying related content, understanding how to efficiently query posts by category is essential for any WordPress developer.

This comprehensive guide covers multiple approaches to extracting post lists from categories, from simple implementations to advanced optimization techniques. By the end, you’ll have a complete toolkit for handling category-based queries in any WordPress project.

Understanding WordPress Categories

Before diving into code, it’s important to understand how WordPress handles categories:

  • Categories are a built-in taxonomy in WordPress
  • Each post can belong to multiple categories
  • Categories can be hierarchical (parent/child relationships)
  • Category data is stored in the wp_terms and wp_term_taxonomy tables
  • Post-category relationships are stored in wp_term_relationships

Understanding this structure helps you write more efficient queries and troubleshoot issues when they arise.

Method 1: WP_Query (The Flexible Approach)

WP_Query is WordPress’s primary class for querying posts. It offers maximum flexibility and is the recommended approach for most use cases.

Basic Category Query

$args = array(
    'category_name' => 'news',
    'posts_per_page' => 10,
    'orderby' => 'date',
    'order' => 'DESC'
);

$query = new WP_Query($args);

if ($query->have_posts()) {
    while ($query->have_posts()) {
        $query->the_post();
        // Display post content
        ?>
        <article>
            <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
            <div class="entry-content">
                <?php the_excerpt(); ?>
            </div>
        </article>
        <?php
    }
    wp_reset_postdata();
}

Query by Category ID

$args = array(
    'cat' => 5, // Category ID
    'posts_per_page' => 5
);

$query = new WP_Query($args);

Multiple Categories

// Posts in ANY of these categories (OR relationship)
$args = array(
    'category__in' => array(5, 10, 15),
    'posts_per_page' => 10
);

// Posts in ALL of these categories (AND relationship)
$args = array(
    'category__and' => array(5, 10),
    'posts_per_page' => 10
);

// Exclude specific categories
$args = array(
    'category__not_in' => array(3, 7),
    'posts_per_page' => 10
);

Including Child Categories

// Get posts from category and all its children
$parent_category_id = 5;

$args = array(
    'cat' => $parent_category_id,
    'posts_per_page' => 20
);

// WP_Query automatically includes child categories when using 'cat'

Method 2: get_posts() (The Simple Approach)

For simpler use cases, get_posts() provides a more straightforward API.

Basic Usage

$posts = get_posts(array(
    'category' => 5,
    'posts_per_page' => 10,
    'orderby' => 'date',
    'order' => 'DESC'
));

foreach ($posts as $post) {
    setup_postdata($post);
    ?>
    <article>
        <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
    </article>
    <?php
}
wp_reset_postdata();

With Category Name

$posts = get_posts(array(
    'category_name' => 'technology',
    'numberposts' => 5
));

Method 3: Shortcodes for Content Editors

Creating a shortcode allows content editors to insert category post lists anywhere.

function category_posts_shortcode($atts) {
    $atts = shortcode_atts(array(
        'category' => '',
        'posts' => 5,
        'orderby' => 'date',
        'order' => 'DESC'
    ), $atts);
    
    $args = array(
        'category_name' => $atts['category'],
        'posts_per_page' => intval($atts['posts']),
        'orderby' => $atts['orderby'],
        'order' => $atts['order']
    );
    
    $query = new WP_Query($args);
    
    ob_start();
    
    if ($query->have_posts()) {
        echo '<div class="category-posts-list">';
        while ($query->have_posts()) {
            $query->the_post();
            ?>
            <article class="category-post">
                <h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
                <p><?php the_excerpt(); ?></p>
            </article>
            <?php
        }
        echo '</div>';
    } else {
        echo '<p>No posts found in this category.</p>';
    }
    
    wp_reset_postdata();
    
    return ob_get_clean();
}
add_shortcode('category_posts', 'category_posts_shortcode');

Usage: [category_posts category="news" posts="5"]

Method 4: Modifying the Main Query

When you want to change which posts appear on category archive pages, use the pre_get_posts action.

function modify_category_queries($query) {
    // Only modify category archives on the main query
    if ($query->is_category() && $query->is_main_query() && !is_admin()) {
        // Show 20 posts per page instead of default
        $query->set('posts_per_page', 20);
        
        // Exclude posts from specific category on certain category pages
        $current_cat = get_queried_object();
        if ($current_cat->slug === 'featured') {
            $query->set('category__not_in', array(10)); // Exclude category ID 10
        }
    }
}
add_action('pre_get_posts', 'modify_category_queries');

Performance Optimization

1. Use Transients for Expensive Queries

function get_cached_category_posts($category_id, $count = 5) {
    $cache_key = 'cat_posts_' . $category_id . '_' . $count;
    $posts = get_transient($cache_key);
    
    if (false === $posts) {
        $args = array(
            'cat' => $category_id,
            'posts_per_page' => $count
        );
        
        $query = new WP_Query($args);
        $posts = $query->posts;
        
        // Cache for 1 hour
        set_transient($cache_key, $posts, HOUR_IN_SECONDS);
    }
    
    return $posts;
}

2. Optimize Database Queries

// Only retrieve fields you need
$args = array(
    'category_name' => 'news',
    'posts_per_page' => 10,
    'fields' => 'ids' // Only get post IDs for better performance
);

$query = new WP_Query($args);

3. Use Object Caching

If your site uses an object cache (Redis, Memcached), WP_Query results are automatically cached, improving performance for repeated queries.

Advanced Techniques

Custom Templates for Category Archives

Create a template file category-news.php for specific category styling:

<?php
/* Template Name: Category - News */
get_header(); ?>

<div class="category-archive">
    <h1><?php single_cat_title(); ?></h1>
    
    <?php if (have_posts()) : ?>
        <div class="posts-grid">
            <?php while (have_posts()) : the_post(); ?>
                <?php get_template_part('content', 'category'); ?>
            <?php endwhile; ?>
        </div>
        
        <?php the_posts_pagination(); ?>
    <?php else : ?>
        <p>No posts found in this category.</p>
    <?php endif; ?>
</div>

<?php get_footer(); ?>

AJAX Loading for Category Posts

For better user experience, implement AJAX loading:

jQuery(document).ready(function($) {
    $('.load-more').on('click', function() {
        var button = $(this);
        var category = button.data('category');
        var page = button.data('page');
        
        $.ajax({
            url: ajaxurl,
            type: 'POST',
            data: {
                action: 'load_category_posts',
                category: category,
                page: page
            },
            success: function(response) {
                $('.posts-container').append(response);
                button.data('page', page + 1);
            }
        });
    });
});

Common Pitfalls to Avoid

  1. Not resetting post data: Always call wp_reset_postdata() after custom loops
  2. Querying on every page load: Use caching for expensive queries
  3. Not checking if posts exist: Always verify have_posts() before looping
  4. Modifying the main query incorrectly: Use pre_get_posts instead of creating new queries on archive pages
  5. Ignoring pagination: Remember to handle pagination for large category archives

Conclusion

WordPress provides multiple ways to extract posts from categories, each suited to different scenarios:

  • WP_Query: Best for complex, custom displays
  • get_posts(): Ideal for simple post lists
  • Shortcodes: Perfect for content editor flexibility
  • pre_get_posts: Essential for modifying archive pages

Understanding these methods and when to use each one will make you a more effective WordPress developer. Remember to always consider performance, especially on sites with large amounts of content.

For production sites, implement caching strategies and test your queries with tools like Query Monitor to ensure optimal performance.

Article FAQ

Frequently Asked Questions

Practical answers to apply the topic in real execution.

SEO-ready GEO-ready AEO-ready 4 Q&A
Do I need to be an experienced developer to use this guide?
This guide is designed for intermediate WordPress developers. Basic PHP and WordPress knowledge is assumed, but all code is explained step-by-step.
Will these techniques work with any WordPress theme?
Most techniques are theme-agnostic and work with standard WordPress themes. However, some heavily customized themes may require additional adaptation.
Is it safe to implement these changes on a live site?
Always test changes on a staging environment first. While these techniques are production-ready, testing prevents potential issues on live sites.
Do these methods work with page builders like Elementor?
Yes, most methods work alongside page builders. Some may require specific integration steps which are noted in the implementation section.

Need an FAQ tailored to your industry and market? We can build one aligned with your business goals.

Let’s discuss

Related Articles