If you’re a WordPress developer, you’ve probably wanted to display posts, custom post types, or specific content in a custom way on your site. That’s exactly where WP_Query comes in. It’s a core WordPress PHP class that lets you fetch posts programmatically and display them wherever you need.
In this tutorial, we’ll walk step-by-step through using WP_Query to fetch custom content, filter it by categories or custom fields, and display it on the frontend.
Before we start coding:
- Always use a child theme or a plugin like Code Snippets to add custom PHP code.
- This prevents breaking your site when the parent theme updates.
Step 1: Create a Custom Post Type (CPT) for Projects
Custom Post Types allow you to store content separately from regular posts or pages. For example, we’ll create a CPT called Projects for portfolio items, case studies, or client projects.
function register_project_cpt() { $args = [ 'public' => true, 'label' => 'Projects', 'has_archive' => true, 'supports' => ['title','editor','thumbnail','custom-fields'], 'rewrite' => ['slug' => 'project'], ]; register_post_type('project', $args); // Register taxonomy for project categories register_taxonomy('project_category', 'project', [ 'label' => 'Project Categories', 'hierarchical' => true, 'public' => true, ]); } add_action('init', 'register_project_cpt');
The code adds a new menu called Projects to your WordPress dashboard that allows you to create projects with titles, content, featured images, and custom fields. It also registers a taxonomy called ‘Project Categories’.

Let’s add Project Categories (like Featured or Highlighted) to organize your projects.

Step 2: Fetch Projects by Category Using WP_Query
Now that we have a Projects Custom Post Type, let’s fetch only certain projects, say, those in the Featured or Highlighted category. Let’s fetch only certain projects (Featured or Highlighted) by defining a tax_query
filter and passing it to WP_Query
, which will run the query and return the matching posts.
add_shortcode('projects', function() { $args = [ 'post_type' => 'project', // Query our 'project' custom post type 'posts_per_page' => 10, // Limit the number of projects 'tax_query' => [ // Filter projects by category [ 'taxonomy' => 'project_category', 'field' => 'slug', // Using category slugs 'terms' => ['featured', 'highlighted'], // Only fetch these categories 'operator' => 'IN', ] ] ]; $projects = new WP_Query($args); // Run the query if ($projects->have_posts()) { // Check if any projects were found ob_start(); // Start output buffering echo '<ul class="projects-grid">'; while ($projects->have_posts()) { $projects->the_post(); // Setup post data echo '<li><a href="'.esc_url(get_permalink()).'">'.esc_html(get_the_title()).'</a></li>'; // Display title with link } echo '</ul>'; wp_reset_postdata(); // Reset post data so other loops aren’t affected return ob_get_clean(); // Return the HTML to display on the page } return '<p>No projects found.</p>'; // Fallback if no projects exist });
Simply place the shortcode [featured_projects]
on any page or post, and you will see only certain projects, those in the Featured or Highlighted category

Step 3: Meta_Query of WP_Query
Sometimes filtering posts by categories or meta keys is not enough. You may want to control the order of your posts based on multiple custom fields.
WP_Query allows this using meta_query with named clauses. Each meta key can be assigned a clause name, which can then be referenced in the orderby parameter. This approach is particularly powerful when you need multi-level sorting based on numeric values, dates, or even text fields.
This is easily done by adding a meta_query and an orderby array to your $args.
$args = [ 'post_type' => 'project', // Query 'project' custom post type 'posts_per_page' => 10, // Limit the number of projects 'tax_query' => [ // Filter projects by categories [ 'taxonomy' => 'project_category', 'field' => 'slug', 'terms' => ['featured', 'highlighted'], 'operator' => 'IN', ] ], 'meta_query' => [ // Multi-meta ordering 'priority_clause' => [ 'key' => 'priority_order', 'type' => 'NUMERIC', 'compare' => 'EXISTS' ], 'status_clause' => [ 'key' => 'status', // Capitalized to match actual meta key 'compare' => 'EXISTS' ], ], 'orderby' => [ // Order by multiple meta keys 'priority_clause' => 'ASC', 'status_clause' => 'DESC', // Active before Inactive (Z→A) ] ]; $projects = new WP_Query($args); // Run the query
Our projects also have two custom fields:
- priority_order (a number)
- status (either
active
orinactive
)

We can use meta_query
to fetch Projects based on these fields and even sort them.

If you’d like to see everything we covered, registering the custom post type, querying by taxonomy, and advanced filtering with meta_query in action, check out the video below.