# 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](https://developer.wordpress.org/plugins/post-types/registering-custom-post-types/), how to [retrieve their content from the database, and how to render them to the public](https://developer.wordpress.org/plugins/post-types/working-with-custom-post-types/).

# 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()`](https://developer.wordpress.org/reference/functions/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()`](https://developer.wordpress.org/reference/functions/register_post_type/) for the description of arguments.

[warning]You must call `register_post_type()` before the [`admin_init`](https://developer.wordpress.org/reference/hooks/admin_init/) hook and after the [`after_setup_theme`](https://developer.wordpress.org/reference/hooks/after_setup_theme/) hook. A good hook to use is the [`init`](https://developer.wordpress.org/reference/hooks/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](https://developer.wordpress.org/themes/core-concepts/theme-structure/) 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()`](https://developer.wordpress.org/reference/functions/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()`](https://developer.wordpress.org/reference/functions/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`](https://developer.wordpress.org/reference/hooks/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');
```