The post shortcode can be used to display a secondary loop of posts anywhere using WP_Query.

[posts] shortcode arguments
Type -
Items -
[compact|very compact|divided|relaxed|very relaxed]
Grid -
[divided|vertically divided|celled|internally celled|relaxed|very relaxed]
Cards -
WordPress query string or keyword (Available keywords: related).
Custom template name.
Custom excerpt length (number of words). Set to 0 to disable excerpts. (Default: 55)
Custom thumbnail image size (none|thumbnail|small|medium|large|full). (Default: medium)
Posts header size (tiny|small|medium|large|huge). (Default: small)
Info displayed below post title. (date_author|timeago_author|date|timeago|author|none)
Set to false to disable tags (categories, tags and sticky post indicator).
Set to false to disable the "Read more" link.
Set to true to enable pagination.
Pagination alignment (left|center|right).
Additional classes for the pagination menu.
Pagination next posts link text (default: "Older posts").
Pagination previous posts link text (default: "Newer posts").
Specify post count when query is a keyword (default: 4).
Header text to display above posts (only displayed if there are posts).
Classes for the header.
Content before the posts (only displayed if there are posts).
Content after the posts (only displayed if there are posts).
<posts items query="posts_per_page=3&ignore_sticky_posts=1">
Divided items
<posts divided items query="posts_per_page=3&ignore_sticky_posts=1">
Relaxed items
<posts very relaxed items query="posts_per_page=3&ignore_sticky_posts=1">
<posts grid query="posts_per_page=3&ignore_sticky_posts=1">
Divided grid
<posts divided two column grid query="posts_per_page=4&ignore_sticky_posts=1">
Celled grid
<posts celled two column grid query="posts_per_page=6&ignore_sticky_posts=1">
Internally celled grid
<posts internally celled two column grid query="posts_per_page=6&ignore_sticky_posts=1&meta_key=_thumbnail_id">
<posts cards query="posts_per_page=3&ignore_sticky_posts=1&meta_key=_thumbnail_id">
Cards count
<posts four stackable cards query="posts_per_page=4&ignore_sticky_posts=1">
Basic pagination
<posts divided items query="posts_per_page=3&ignore_sticky_posts=1" pagination="true">
Advanced pagination
<posts two column grid
	next="Older entries"
	prev="Newer entries"
	pagination_menu="tiny secondary"
Content instead of excerpt

Adding a custom post type

For creating a custom post type you should have some basic PHP and WordPress knowledge. Here’s a quick rundown on the steps to take to add a custom post type when using the Chap Theme.

Register the custom post type

In the child theme’s functions.php file (or in a custom plugin) you can register your custom post type. In this case it’s being called custom-post.

namespace Chap\Child {


	add_action('after_setup_theme', function() {
				'description' => esc_html__('A custom post.'),
				'labels' => [
					'name'                  => __('Custom posts'),
					'singular_name'         => __('Custom post'),
					'menu_name'             => __('Custom posts'),
					'name_admin_bar'        => __('Custom post'),
					'add_new'               => __('Add New'),
					'add_new_item'          => __('Add New Custom post'),
					'edit_item'             => __('Edit Custom post'),
					'new_item'              => __('New Custom post'),
					'view_item'             => __('View Custom post'),
					'search_items'          => __('Search Custom posts'),
					'not_found'             => __('No Custom posts found'),
					'not_found_in_trash'    => __('No Custom posts found in trash'),
					'all_items'             => __('All Custom posts'),
					'featured_image'        => __('Custom post image'),
					'set_featured_image'    => __('Set Custom post image'),
					'remove_featured_image' => __('Remove Custom post image'),
					'use_featured_image'    => __('Use as Custom post image'),
					'insert_into_item'      => __('Insert into Custom post'),
					'uploaded_to_this_item' => __('Uploaded to this Custom post'),
					'views'                 => __('Filter Custom posts list'),
					'pagination'            => __('Custom posts list navigation'),
					'list'                  => __('Custom posts list'),
				'menu_icon' => 'dashicons-media-document',
				'capability_type' => 'post',
				'public' => true,
				'hierarchical' => false,
				'has_archive' => false,
				'exclude_from_search' => true,
				'show_in_nav_menus' => false,
				'rewrite' => false,
				'supports' => [
				'show_in_rest' => true,
				'template' => [
					['core/image', [
						'align' => 'left',
					['core/heading', [
						'placeholder' => 'Add Author...',
					['core/paragraph', [
						'placeholder' => 'Add Description...',
					['gsui/button', [
						'primary' => true,
						'large' => true,
					['gsui/divider', [
						'clearing' => true,
						'hidden' => true,
	}, 50);


The last 2 parameters, show_in_rest and template can be omitted if you don’t use the new Gutenberg editor, but if you do, the show_in_rest parameter is what enables the Gutenberg support, and the template parameter is optional, if you wish to have some predefined content when creating a new post of type custom-post.

Adding custom templates

By using a custom template for your custom post type, you are able to display additional information (from options, ACF or similar) near the usual the_content(), title, etc.

First it’s needed to create the file that WordPress will first look for when dealing with a single post of custom-post type – single-{$post_type}.php or in this case single-custom-post.php.

 * Single custom post template.

if(!defined('ABSPATH')) {
	exit; // Exit if accessed directly

<?php if(have_posts()): the_post(); ?>
	<?php get_template_part('templates/content-single-custom-post'); ?>
<?php endif; ?>

The above file simply points to the “real” template that is relevant for the post type, templates/content-single-custom-post.php, which also needs to be created. If your child theme doesn’t have the templates folder you can simply create it, and then create the template file in there:

 * Single custom post content.

if(!defined('ABSPATH')) {
	exit; // Exit if accessed directly

<article <?php post_class('content'); ?>>

	<?php do_action('chap_render_post_title'); ?>

	<?php do_action('chap_render_featured_image'); ?>

	<?php the_content(); ?>


<div class="ui hidden clearing divider"></div>

<?php do_action('chap_after_post_content'); ?>

Now you can edit the content-single-custom-post.php to display any additional information, move things around or remove them.

Customizing the page layout

If you wish to display your custom post type as a full width page, a page with 2 sidebars or anything in between, you can use the chap_page_type filter to assign a type for any pages displaying the custom-post post type.

 * Override page layout for custom post type.
 *   0 - page with no sidebars (but contained to a reasonable max width)
 *   1 - primary sidebar + page
 *   2 - page + primary sidebar
 *   3 - primary sidebar + page + secondary sidebar
 *   4 - secondary sidebar + page + primary sidebar
 *   5 - full width page
add_filter('chap_page_type', function($type) {
	if(get_post_type() === 'custom-post') {
		return 0;
	return $type;
What about AMP

By default for AMP, any custom post types are disabled. You can go to WordPress admin dashboard -> AMP -> General and enable your custom post type under Supported Templates.

When enabled, the custom post type will use the regular amp/single.php template, which will look like any other post. If you wish to customize your custom post type on AMP pages as well, you need to copy the /chap/amp/single.php template over to /chap-child/amp/single-{$post_type}.php and customize that (in this case single-custom-post.php). So essentially, it’s twice the work for AMP support unfortunately. AMP templates are also not using the Sage template wrapping features so they are a bit bulkier.

Additionally you can create a /chap-child/amp/styles/{$post-type}.php file that outputs any CSS to be used on the custom post type’s page.

<posts one column vertically divided grid
	next="Older post"
	prev="Newer post"
The loop-content template is designed to show posts with content inside grid loops.
Custom header size
<posts items query="posts_per_page=3&ignore_sticky_posts=1" header="huge">
No thumbnails
<posts three column grid query="posts_per_page=3&ignore_sticky_posts=1&meta_key=_thumbnail_id" excerpt="20" thumbnail="none">
<posts three column grid query="posts_per_page=3&ignore_sticky_posts=1&meta_key=_thumbnail_id" excerpt="0">
No meta
<posts three column grid query="posts_per_page=3&ignore_sticky_posts=1&meta_key=_thumbnail_id" excerpt="20" meta="false">
No tags
<posts three column grid query="posts_per_page=3&ignore_sticky_posts=1&meta_key=_thumbnail_id" excerpt="20" tags="false">
<posts three column grid query="posts_per_page=3&ignore_sticky_posts=1&meta_key=_thumbnail_id" excerpt="20" read_more="false">
More posts
<subheader dividing>More posts</subheader>
<posts compact marginless items query="posts_per_page=5&ignore_sticky_posts=1" header="tiny" excerpt="0" thumbnail="none" meta="true" tags="false" read_more="false">
<posts query="related" count="5">
Using the "related" keyword as query will fetch related posts.
You can use custom WP_Query arguments programmatically by using the csc_posts_query filter. Argument 2 of that filter will contain the shortcode's arguments which can be used to identify which instance of the shortcode it is.
<posts id="custom">
add_filter('csc_posts_query', function($query, $args) {
	if(isset($args['id']) && $args['id'] === 'custom') {
		return [
			'category__in' => [2, 6],
			'posts_per_page' => 3,
	return $query;
}, 10, 2);
<posts items query="posts_per_page=3&ignore_sticky_posts=1" header_text="Header shown when there are posts" header_classes="large dividing">
<posts items query="posts_per_page=3&cat=123123" header_text="Header not shown because there are no posts">
Header can be prepended with the header_text argument for only when there are results.
Wrapper: content before/after
<posts items query="posts_per_page=3&ignore_sticky_posts=1" content_before="{lt}div class='ui primary segment'{gt}" content_after="{lt}/div{gt}">
<posts items query="posts_per_page=3&cat=123123" content_before="{lt}div class='ui primary segment'{gt}" content_after="{lt}/div{gt}">
You can specify before and after content to wrap the posts in something. The wrapper elements won't be displayed when there are no posts.
That way there won't be an empty container when there are no related posts for example.
<posts three column internally celled stackable grid query="related" count="6" header_text="Related posts" header_classes="large dividing" content_before="{lt}div class='ui segment'{gt}" content_after="{lt}/div{gt}">
Adding this shortcode to Chap Theme -> Code -> After post content can easily display related posts for all posts.

Leave a comment

Your email address will not be published. Required fields are marked *