arrow_back
Back

WordPress post types: register_post_type, templates, and queries

Andrew Dorokhov Andrew Dorokhov schedule 5 min read
menu_book Table of Contents

There are many different types of content in WordPress.

Internally, all of the Post Types are stored in the same place — in the wp_posts database table — but are differentiated by a database column called post_type.

Different Post Types are displayed by different Template files.

Default Post Types

There are several default Post Types readily available to users or internally used by the WordPress installation. The most common are:

  • Post (Post Type: post) — the most used type. Posts support tags and comments and appear in the blog listing and in the RSS feed.
  • Page (Post Type: page) — static pages. They are excluded from the blog listing and the feed, and their templates normally don’t render tags or comments.
  • Attachment (Post Type: attachment) — holds the information about every file uploaded through the standard uploader.
  • Revision (Post Type: revision) — backup copies of posts.
  • Navigation menu (Post Type: nav_menu_item) — menu items are stored as posts too.
  • Block templates (Post Type: wp_template)
  • Template parts (Post Type: wp_template_part)

Creating a post type

Once a custom post type is registered, it gets a new top-level administrative screen that can be used to manage and create posts of that type.

To register a new post type, you use the open_in_new register_post_type() function.

function register_my_custom_post_type() {
    register_post_type( 'product',
        array(
            'labels'    => array(
                'name'          => __( 'Products', 'textdomain' ),
                'singular_name' => __( 'Product', 'textdomain' ),
            ),
            'public'      => true,
            'has_archive' => true,
        )
    );
}

add_action( 'init', 'register_my_custom_post_type' );

Custom slug

To set a custom slug for the slug of your custom post type all you need to do is add a key => value pair to the rewrite key in the register_post_type() arguments array.

function wporg_custom_post_type() {
	register_post_type('wporg_product',
		array(
			'labels'      => array(
				'name'          => __( 'Products', 'textdomain' ),
				'singular_name' => __( 'Product', 'textdomain' ),
			),
			'public'      => true,
			'has_archive' => true,
			'rewrite'     => array( 'slug' => 'products' ), // my custom slug
		)
	);
}

add_action('init', 'wporg_custom_post_type');

Note that register_post_type() must be called inside the init hook — calling it earlier or later will not work.

Labels

labels controls every piece of text WordPress shows for the type in the admin area. A full set looks like this:

$labels = [
    'name'               => 'Clients',
    'singular_name'      => 'Client',
    'add_new'            => 'Add New',
    'add_new_item'       => 'Add New Client',
    'edit_item'          => 'Edit Client',
    'new_item'           => 'New Client',
    'all_items'          => 'All Clients',
    'view_item'          => 'View Client',
    'search_items'       => 'Search Clients',
    'not_found'          => 'No clients found.',
    'not_found_in_trash' => 'No clients found in Trash.',
    'menu_name'          => 'Clients', // the link in the admin menu
];

Admin menu and supported features

$args = [
    'labels'        => $labels,
    'public'        => true,
    'show_ui'       => true,             // show the interface in the admin area
    'has_archive'   => true,
    'menu_icon'     => 'dashicons-groups',
    'menu_position' => 20,               // the position in the admin menu
    'supports'      => [ 'title', 'editor', 'thumbnail' ],
];

register_post_type( 'client', $args );

menu_icon accepts a open_in_new Dashicons name, or a URL to your own image:

'menu_icon' => get_stylesheet_directory_uri() . '/img/function_icon.png',

supports decides which meta boxes appear on the editing screen — title, editor, comments, author, thumbnail, excerpt, custom-fields, revisions and so on.

Enabling the block editor

A custom post type gets the classic editor unless you expose it to the REST API:

$args = [
    'labels'      => $labels,
    'public'      => true,
    'show_ui'     => true,
    'has_archive' => true,
    // ...
    'show_in_rest' => true, // Here it is.
];

Attaching taxonomies

A custom post type can reuse the built-in categories:

'taxonomies' => [ 'category' ],

Be aware that the categories will then be shared with regular posts, which is not always what you want — in that case register a custom taxonomy instead.

Once the taxonomy is attached, both types can be queried together:

query_posts( [
    'category_name' => $category->slug,
    'post_type'     => [ 'post', 'success-story' ],
] );

Flushing the rewrite rules

Permalinks for a new post type will return 404 until the rewrite rules are rebuilt. It’s enough to do it once, normally from the plugin activation hook:

flush_rewrite_rules( false );

Naming

Use hyphens in the post type name (success-story rather than success_story), because the name ends up in the URL.

Using in a template

The following templates can display Custom post types:

  • single-{post-type}
  • archive-{post-type}
  • search
  • index

Add a new record

$post_data = array(
    'post_title'    => 'New Post Title',
    'post_content'  => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
    'post_status'   => 'publish',
    'post_type'     => 'custom_post_type', // Replace with your custom post type slug
);

$post_id = wp_insert_post($post_data);

if (!is_wp_error($post_id)) {
    // Post inserted successfully, you can perform additional actions here
    echo 'New post added with ID: ' . $post_id;
} else {
    // Handle the error
    echo 'Error adding new post: ' . $post_id->get_error_message();
}

Queries

Use open_in_new WP_Query class for such operations.

$query = new WP_Query( [
    'title' 	=> $name,
    'post_type' => 'employer',
] );

if ( $query->have_posts() ) {
    return true;
}

To render the results, run a secondary loop over the query object and reset the global post data afterwards:

<?php
$clients = new WP_Query( [
    'post_type'      => 'client',
    'posts_per_page' => -1,
] );

while ( $clients->have_posts() ) : $clients->the_post(); ?>

    ...

<?php endwhile; wp_reset_postdata(); ?>

Show the existing post types

You can use this request to see the existing post types:

SELECT post_type, COUNT(*) AS total_posts
FROM wp_posts
GROUP BY post_type
ORDER BY total_posts DESC;

If you want to sort post types (post_type) by the date of their first appearance (i.e., by the minimum ID), here’s the appropriate SQL query:

SELECT post_type, COUNT(*) AS total_posts, MIN(ID) AS first_post_id
FROM wp_posts
GROUP BY post_type
ORDER BY first_post_id ASC;
code

Need Help with Development?

Happy to help — reach out via the contacts or go straight to my Upwork profile.

work View Upwork Profile arrow_forward
Next Article

WordPress hooks: actions, filters, add_action, and add_filter

arrow_forward