Shortcode to List Custom Post Type Entries in WordPress

Quick Summary

  • Register a post_list shortcode that runs a WP_Query for your custom post type and returns each entry's title, content and one custom field.
  • Replace posttype and custom_field with your post type slug and meta key, then add the shortcode with count="5"; without count it follows the Reading setting, and -1 shows all.
  • Content goes through the_content so blocks and shortcodes render, the custom field is escaped with esc_html(), and wp_reset_postdata() restores the page's post.

To list entries from a custom post type inside a page or post, register a shortcode that runs a WP_Query for that post type and returns the HTML. The [post_list] shortcode below shows each entry’s title, content and one custom field, sorted by the Order field, and takes a count attribute for how many to show. Add the code to your child theme’s functions.php or to a small custom plugin.

PHP
function post_list_shortcode( $atts ) {
    $atts = shortcode_atts( array(
        'count' => '',
    ), $atts, 'post_list' );

    $q = new WP_Query( array(
        'post_type'      => 'posttype',
        'posts_per_page' => (int) $atts['count'],
        'orderby'        => 'menu_order',
        'order'          => 'ASC',
    ) );

    $list = '<div class="custom_post_list">';
    while ( $q->have_posts() ) : $q->the_post();
        $custom_field = get_post_meta( get_the_ID(), 'custom_field', true );
        $list .= '
        <div class="single_post_item">
            <h2>' . get_the_title() . '</h2>
            ' . apply_filters( 'the_content', get_the_content() ) . '
            <p>' . esc_html( $custom_field ) . '</p>
        </div>';
    endwhile;
    $list .= '</div>';
    wp_reset_postdata();

    return $list;
}
add_shortcode( 'post_list', 'post_list_shortcode' );

How to use it

Before using it, change these values to match your site:

  • 'posttype': the slug of your custom post type, the key you used when you registered the post type with register_post_type().
  • 'custom_field': the meta key of the custom field you want to show. If you don’t need one, remove that line and the <p> that prints it.
  • post_list in add_shortcode(): the shortcode name, if it clashes with another plugin or you want a different one.
  • custom_post_list and single_post_item: the class names, to match your CSS.

Then put [post_list count="5"] in the content to show five entries. In the block editor, use a Shortcode block.

  • Without count, the list shows as many entries as Settings > Reading > Blog pages show at most.
  • count="-1" shows all entries.

In a test on this site, with post in place of 'posttype', [post_list count="2"] returned two items, [post_list] returned 12 (the site’s Reading setting) and [post_list count="-1"] returned every published post.

How it works

  • shortcode_atts() merges the attributes from the shortcode with the default (an empty count). The third argument, 'post_list', also lets other code change the attributes through the shortcode_atts_post_list filter.
  • (int) turns count into a number for posts_per_page. An empty value becomes 0, which WP_Query replaces with the Reading setting.
  • Entries are sorted by menu_order, lowest first. That is the Order field, which appears in the editor when the post type supports page-attributes.
  • The function collects the HTML in $list and returns it. A shortcode has to return its output; if it echoes, the output usually ends up above the rest of the content.
  • The raw content from get_the_content() goes through the the_content filter, the same one WordPress uses for a normal post. That renders blocks, adds paragraph tags to classic content with wpautop() and runs shortcodes. Plugins that add things to post content, such as share buttons, may add them to each entry too; if you only need paragraphs, use wpautop( get_the_content() ) instead.
  • The custom field goes through esc_html(), so HTML or script saved in the field is shown as text instead of running on the page. See when to use esc_html, esc_attr and esc_url.
  • wp_reset_postdata() restores the global post of the main query after the custom loop, so the rest of the page, such as comments or other shortcodes, works with the right post again.

Things to check

  • Don’t put [post_list] inside the content of an entry it lists. Because the content’s shortcodes run, that entry would render the list again and again.
  • The query returns published entries, plus private ones for logged-in users who are allowed to read them. Drafts and scheduled entries don’t appear.
  • If you don’t need custom HTML, the core Query Loop block can list a custom post type without code, as long as the post type is public (viewable) and registered with show_in_rest set to true.
  • When the list gets long, add page links; see the paginated version below.
Share on X (opens in a new tab) Share on LinkedIn (opens in a new tab) Share on Facebook (opens in a new tab)

ChatGPT (opens in a new tab) Claude (opens in a new tab) Gemini (opens in a new tab) Perplexity (opens in a new tab) Grok (opens in a new tab)

More Insights

See More