Skip to main content
Post types is how we can organize content in WordPress into different “folders”. We can separate Articles from regular pages or even create custom post types to hold things like Events and they will all be separate from each other. Each with its own set of fields and settings. We’ll go over the basics of post types and how to create custom post types.

Built-in Post Types

WordPress comes with 3 built-in post types: post, page and attachment. Posts are usually meant for articles. Pages are used for content like About or Contact Us. Attachments are used for media files. Everytime you upload a file to WordPress, it creates an attachment post. Even though WordPress supports these three different content types, under the hood, all of them are stored in the same database table. The differences between them are based on the features they support and the possible ways to retrieve them. When architecting a site, have the arguments for register_post_type() in front of you, as well as the list of post type features. Consciously decide which capabilities that the different post types need to support, and which not. Keep in mind that once you decide to give a capability to a content type, it will apply to all entities of this type. Imagine that you have a post type and you registered archive support. It will be difficult to exclude individual posts from this archive (there are possible approaches, but they all have important drawbacks). This is because every post type argument and feature comes with a fixed set of expectations. As an example if a post type supports authors, it will show up in author archives. While it is possible to remove individual post types from individual archives, this can be ineffective, or even impossible. The decision on whether to use a built-in WordPress feature and customise it, or whether to write something from scratch is not always clear from the start. In such a case, it can be useful to build a quick MVP to validate or refute a possible architecture.

Registering a new Custom Post Type

To register a new post type, you can use the register_post_type() function. This function takes two parameters: the post type name and an array of arguments. We register all post types in plugins. One plugin per post type. You can create new post types by using our Post Type Plugin Generator but you can also find a sample plugin file below.
Please remember to replace PREFIX with your own project prefix and replace textdomain with your own text domain.
PluginName.php
<?php
/**
 * Plugin Name: PREFIX Post Types - Press Releases
 * Plugin URI: http://trewknowledge.com/
 * Description: A plugin to register the Press Releases custom post type.
 * Version: 1.0.0
 * Author: Trew Knowledge
 * Author URI: http://trewknowledge.com/
 * License: GPL-2.0+
 * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
 * Text Domain: textdomain
 */

add_action( 'init', function() {

  /* 
    Load Load Translations
  */

  load_plugin_textdomain( 'textdomain', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );

  /*
    Register Press Release Post Type
  */

  $labels = array(
    'name'                  => _x( 'Press Releases', 'Post type general name', 'textdomain' ),
    'singular_name'         => _x( 'Press Release', 'Post type singular name', 'textdomain' ),
    'menu_name'             => _x( 'Press Releases', 'Admin Menu text', 'textdomain' ),
    'name_admin_bar'        => _x( 'Press Release', 'Add New on Toolbar', 'textdomain' ),
    'add_new'               => __( 'Add New', 'textdomain' ),
    'add_new_item'          => __( 'Add New Press Release', 'textdomain' ),
    'new_item'              => __( 'New Press Release', 'textdomain' ),
    'edit_item'             => __( 'Edit Press Release', 'textdomain' ),
    'view_item'             => __( 'View Press Release', 'textdomain' ),
    'all_items'             => __( 'All Press Releases', 'textdomain' ),
    'search_items'          => __( 'Search Press Releases', 'textdomain' ),
    'parent_item_colon'     => __( 'Parent Press Releases:', 'textdomain' ),
    'not_found'             => __( 'No Press Releases found.', 'textdomain' ),
    'not_found_in_trash'    => __( 'No Press Releases found in Trash.', 'textdomain' ),
    'featured_image'        => _x( 'Press Release Cover Image', 'Overrides the “Featured Image” phrase for this post type. Added in 4.3', 'textdomain' ),
    'set_featured_image'    => _x( 'Set cover image', 'Overrides the “Set featured image” phrase for this post type. Added in 4.3', 'textdomain' ),
    'remove_featured_image' => _x( 'Remove cover image', 'Overrides the “Remove featured image” phrase for this post type. Added in 4.3', 'textdomain' ),
    'use_featured_image'    => _x( 'Use as cover image', 'Overrides the “Use as featured image” phrase for this post type. Added in 4.3', 'textdomain' ),
    'archives'              => _x( 'Press Release archives', 'The post type archive label used in nav menus. Default “Post Archives”. Added in 4.4', 'textdomain' ),
    'insert_into_item'      => _x( 'Insert into Press Release', 'Overrides the “Insert into post”/”Insert into page” phrase (used when inserting media into a post). Added in 4.4', 'textdomain' ),
    'uploaded_to_this_item' => _x( 'Uploaded to this Press Release', 'Overrides the “Uploaded to this post”/”Uploaded to this page” phrase (used when viewing media attached to a post). Added in 4.4', 'textdomain' ),
    'filter_items_list'     => _x( 'Filter Press Releases list', 'Screen reader text for the filter links heading on the post type listing screen. Default “Filter posts list”/”Filter pages list”. Added in 4.4', 'textdomain' ),
    'items_list_navigation' => _x( 'Press Releases list navigation', 'Screen reader text for the pagination heading on the post type listing screen. Default “Posts list navigation”/”Pages list navigation”. Added in 4.4', 'textdomain' ),
    'items_list'            => _x( 'Press Releases list', 'Screen reader text for the items list heading on the post type listing screen. Default “Posts list”/”Pages list”. Added in 4.4', 'textdomain' ),
  );

  $args = array(
    'labels'             => $labels,
    'description'        => 'Press Release custom post type.',
    'public'             => true,
    'publicly_queryable' => false,
    'show_ui'            => true,
    'show_in_menu'       => true,
    'query_var'          => false,
    'rewrite'            => array( 'slug' => 'press-release' ),
    'capability_type'    => 'post',
    'has_archive'        => false,
    'hierarchical'       => false,
    'menu_position'      => 20,  
    'supports'           => array( 'title', 'editor', 'excerpt', 'author', 'revisions', 'custom-fields' ),
    'show_in_rest'       => true
  );

  register_post_type( 'prefix_press_release', $args );

} );