Post Types

Custom Post Types
WordPress stores the Post Types in the 
posts table allowing developers to register Custom Post Types along the ones that already exist.
This chapter will show you how to register Custom Post Types, how to retrieve their content from the database, and how to render them to the public.

Registering Custom Post Types
WordPress comes with five default post types: 
post, 
page, 
attachment, 
revision, and 
menu.
While developing your plugin, you may need to create your own specific content type: for example, products for an e-commerce website, assignments for an e-learning website, or movies for a review website.
Using Custom Post Types, you can register your own 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 
register_post_type() function.
[alert]We recommend that you put custom post types in a plugin rather than a theme. This ensures that user content remains portable even if the theme is changed.[/alert]
The following minimal example registers a new post type, Products, which is identified in the database as 
wporg_product.
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,
    )
  );
}
add_action('init', 'wporg_custom_post_type');
Please visit the reference page for 
register_post_type() for the description of arguments.
[warning]You must call 
register_post_type() before the 
admin_init hook and after the 
after_setup_theme hook. A good hook to use is the 
init action hook.[/warning]
Naming Best Practices
It is important that you prefix your post type functions and identifiers with a short prefix that corresponds to your plugin, theme, or website.
[warning]Make sure your custom post type identifier does not exceed 20 characters as the 
post_type column in the database is currently a VARCHAR field of that length.[/warning]
[warning]To ensure forward compatibility, do not use 
wp_ as your identifier — it is being used by WordPress core.[/warning]
[warning]If your identifier is too generic (for example: 
product), it may conflict with other plugins or themes that have chosen to use that same identifier.[/warning]
[info]Solving duplicate post type identifiers is not possible without disabling one of the conflicting post types.[/info]
URLs
A custom post type gets its own slug within the site URL structure.
A post of type 
wporg_product will use the following URL structure by default: 
https://example.com/wporg_product/%product_name%.
wporg_product is the slug of your custom post type and 
%product_name% is the slug of your particular product.
The final permalink would be: 
https://example.com/wporg_product/wporg-is-awesome.
You can see the permalink on the edit screen for your custom post type, just like with default post types.
A Custom Slug for a Custom Post Type
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.
Example:
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');
The above will result in the following URL structure: 
https://example.com/products/%product_name%
[warning]Using a generic slug like 
products can potentially conflict with other plugins or themes, so try to use one that is more specific to your content.[/warning]
[info]Unlike the custom post type identifiers, the duplicate slug problem can be solved easily by changing the slug for one of the conflicting post types.[/info]
If the plugin author included an 
apply_filters() call on the arguments, this can be done programmatically by overriding the arguments submitted via the 
register_post_type() function.

Working with Custom Post Types
Custom Post Type Templates
You can create custom templates for your custom post types. In the same way posts and their archives can be displayed using 
single.php and 
archive.php, you can create the templates:
single-{post_type}.php – for single posts of a custom post type
archive-{post_type}.php – for the archive
Where 
{post_type} is the post type identifier, used as the 
$post_type argument of the 
register_post_type() function.
Building upon what we’ve learned previously, you could create 
single-wporg_product.php and 
archive-wporg_product.php template files for single product posts and the archive.
Alternatively, you can use the 
is_post_type_archive() function in any template file to check if the query shows an archive page of a given post type, and the 
post_type_archive_title()  function to display the post type title.
Querying by Post Type
You can query posts of a specific type by passing the 
post_type key in the arguments array of the 
WP_Query class constructor.
<?php
$args = array(
  'post_type'      => 'product',
  'posts_per_page' => 10,
);
$loop = new WP_Query($args);
while ( $loop->have_posts() ) {
  $loop->the_post();
  ?>
  <div class="entry-content">
    <?php the_title(); ?>
    <?php the_content(); ?>
  </div>
  <?php
}
This loops through the latest ten product posts and displays the title and content of them one by one.
Altering the Main Query
Registering a custom post type does not mean it gets added to the main query automatically.
If you want your custom post type posts to show up on standard archives or include them on your home page mixed up with other post types, use the 
pre_get_posts action hook.
The next example will show posts from 
post, 
page and 
movie post types on the home page:
function wporg_add_custom_post_types($query) {
  if ( is_home() && $query->is_main_query() ) {
    $query->set( 'post_type', array( 'post', 'page', 'movie' ) );
  }
  return $query;
}
add_action('pre_get_posts', 'wporg_add_custom_post_types');