WordPress shortcodes allow you to add dynamic content and functionality to pages, posts, and other areas of your website without writing the same code repeatedly.
One useful application is displaying a list of blog posts or other post types on a specific page. For example, you may want to display the latest blog posts, show pages belonging to a particular parent page, or list posts from a custom post type.
You can create your own shortcode using the WordPress Shortcode API and the WP_Query class. In this tutorial, we will create a shortcode that accepts query attributes and displays matching posts with their titles, featured images, and excerpts.
1. Register a Custom WordPress Shortcode
WordPress provides the add_shortcode() function to register custom shortcodes. You need to provide a shortcode name and a callback function that generates its output.
For example, the following code registers a shortcode named loop_shortcode:
/**
* Register a custom post loop shortcode.
*/
function geekerhub_custom_query_shortcode( $atts ) {
// Shortcode functionality will go here.
return '';
}
add_shortcode(
'loop_shortcode',
'geekerhub_custom_query_shortcode'
);
Once registered, you can use the shortcode in your WordPress content:
[loop_shortcode]
To make the shortcode more flexible, we can add attributes that control which posts WordPress should display.
2. Add Attributes to the Shortcode
Shortcode attributes allow users to pass values into the callback function. For this example, we will support the following attributes:
- post_type: The post type to retrieve, such as
post,page, or a custom post type. - posts_per_page: The maximum number of posts to display.
- post_parent: The parent page ID, useful when displaying child pages.
- orderby: The field used to order the results.
- order: The sorting direction, either ascending or descending.
We can define default values using shortcode_atts():
$atts = shortcode_atts(
array(
'post_type' => 'post',
'posts_per_page' => 10,
'post_parent' => 0,
'orderby' => 'date',
'order' => 'DESC',
),
$atts,
'loop_shortcode'
);
If a visitor does not provide an attribute, WordPress uses the corresponding default value.
3. Create a WordPress Shortcode to Display Blog Posts
Now we can combine the shortcode registration, attributes, and post query into one function.
Add the complete code below to your active child theme’s functions.php file or a custom plugin:
/**
* Display posts using a custom WordPress shortcode.
*
* Example:
* [loop_shortcode post_type="post" posts_per_page="10"]
*/
function geekerhub_custom_query_shortcode( $atts ) {
$atts = shortcode_atts(
array(
'post_type' => 'post',
'posts_per_page' => 10,
'post_parent' => 0,
'orderby' => 'date',
'order' => 'DESC',
),
$atts,
'loop_shortcode'
);
// Sanitize shortcode attributes.
$post_type = sanitize_key( $atts['post_type'] );
$posts_per_page = absint( $atts['posts_per_page'] );
$post_parent = absint( $atts['post_parent'] );
// Allow only supported ordering fields.
$allowed_orderby = array(
'date',
'title',
'ID',
'modified',
'menu_order',
'rand',
);
$orderby = in_array(
$atts['orderby'],
$allowed_orderby,
true
) ? $atts['orderby'] : 'date';
// Allow only ASC or DESC.
$order = strtoupper( $atts['order'] );
if ( ! in_array( $order, array( 'ASC', 'DESC' ), true ) ) {
$order = 'DESC';
}
// Prevent invalid or unlimited queries.
if ( $posts_per_page < 1 ) {
$posts_per_page = 10;
}
$query_args = array(
'post_type' => $post_type,
'post_status' => 'publish',
'posts_per_page' => $posts_per_page,
'orderby' => $orderby,
'order' => $order,
'ignore_sticky_posts' => true,
);
// Apply parent page filtering when an ID is provided.
if ( 'page' === $post_type && $post_parent > 0 ) {
$query_args['post_parent'] = $post_parent;
}
$posts_query = new WP_Query( $query_args );
if ( ! $posts_query->have_posts() ) {
return '<p>No posts found.</p>';
}
$output = '<div class="geekerhub-post-list">';
while ( $posts_query->have_posts() ) {
$posts_query->the_post();
$output .= '<article class="geekerhub-post-item">';
// Display the featured image.
if ( has_post_thumbnail() ) {
$output .= '<a href="' . esc_url( get_permalink() ) . '">';
$output .= get_the_post_thumbnail(
get_the_ID(),
'medium'
);
$output .= '</a>';
}
// Display the post title.
$output .= '<h3>';
$output .= '<a href="' . esc_url( get_permalink() ) . '">';
$output .= esc_html( get_the_title() );
$output .= '</a>';
$output .= '</h3>';
// Display the post excerpt.
$output .= '<div class="geekerhub-post-excerpt">';
$output .= wp_kses_post( get_the_excerpt() );
$output .= '</div>';
$output .= '</article>';
}
$output .= '</div>';
// Restore the original global post data.
wp_reset_postdata();
return $output;
}
add_shortcode(
'loop_shortcode',
'geekerhub_custom_query_shortcode'
);
This shortcode uses WP_Query to retrieve published posts based on the supplied attributes. It then builds an HTML list containing each post’s featured image, title, and excerpt.
It also uses wp_reset_postdata() after the custom loop to restore the original post data. This is important when running a secondary query inside a page or post.
4. Display the Latest Blog Posts
After adding the PHP code, you can display the latest blog posts by using this shortcode:
[loop_shortcode post_type="post" posts_per_page="10"]
This displays up to 10 published blog posts, ordered by publication date from newest to oldest.
You can change the number of posts by updating the posts_per_page attribute.
5. Display Child Pages of a Specific Parent Page
You can also use the shortcode to display pages belonging to a particular parent page.
For example, the following shortcode displays up to 100 published child pages of the page with ID 453:
[loop_shortcode post_type="page" posts_per_page="100" post_parent="453"]
Replace 453 with the actual parent page ID. The shortcode will retrieve pages whose direct parent is that page.
6. Change the Sorting Order
The shortcode also supports orderby and order attributes. You can use them to change how the results are sorted.
For example, display blog posts alphabetically by title:
[loop_shortcode post_type="post" posts_per_page="10" orderby="title" order="ASC"]
Or display the most recently modified posts first:
[loop_shortcode post_type="post" posts_per_page="10" orderby="modified" order="DESC"]
The example code supports these ordering fields: date, title, ID, modified, menu_order, and rand.
7. Add CSS to Style the Displayed Posts
The shortcode generates HTML with CSS classes so you can customize its appearance. Add the following CSS to your theme’s stylesheet or the WordPress Additional CSS section:
.geekerhub-post-list {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 24px;
}
.geekerhub-post-item img {
display: block;
width: 100%;
height: auto;
}
.geekerhub-post-item h3 {
margin: 12px 0;
}
.geekerhub-post-item h3 a {
text-decoration: none;
}
.geekerhub-post-excerpt {
line-height: 1.6;
}
@media (max-width: 768px) {
.geekerhub-post-list {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 480px) {
.geekerhub-post-list {
grid-template-columns: 1fr;
}
}
This creates a responsive post grid with three columns on wider screens, two columns on smaller screens, and one column on mobile devices.
Important Notes
- Use a custom plugin: If you want the shortcode to remain available after changing your theme, place the PHP code in a custom plugin rather than your theme’s
functions.phpfile. See our guide on creating a custom plugin in WordPress. - Published posts only: The example uses
post_status => publish, so draft and private posts are not included. - Custom post types: You can pass a registered custom post type through the
post_typeattribute. - Parent page filtering: The
post_parentattribute is applied when the selected post type ispage. - Large queries: Avoid displaying an unnecessarily large number of posts on one page, as this can affect page loading performance.
- Shortcode output: The callback returns HTML rather than directly printing it. This is important because WordPress shortcodes are expected to return their output.
For more information about creating custom shortcodes and querying posts, refer to the official WordPress documentation for add_shortcode() and WP_Query.
Conclusion
You can display blog posts, pages, and custom post types using a custom WordPress shortcode. By combining add_shortcode(), shortcode_atts(), and WP_Query, you can create a reusable shortcode that accepts different query attributes.
Use the shortcode to display the latest blog posts, list child pages of a parent page, or sort posts by title and modification date. You can also customize the generated HTML and CSS to match your website’s design.