Custom post types are one of the most crucial feature in WordPress. The way a developer can manipulate the platform’s API is fantastic, and complex systems can be created from developing new types, contents and structures.

Having that said, the mechanism for adding and altering post types in WordPress hasn’t changed much over the years. Indeed they added new arguments and properties to set up post types, but the way they are added and managed basically remains the same.

As a manner to convey repetitive code generated from adding multiple custom post types, developers usually put up wrappers and helpers to make this task less redundant. So, at this point, one should ask: why building one more?

Support and maintenance

As most classes and helpers created for adding custom post types are basically built as tools for those building websites, they are frequently abandoned or left to the community, with rare or no updates.

A wrapper which evolves and adds constantly new features and possibilities is still a need. Everyday, devs and programmers find new ways of using custom post types. So, let’s track all improvements to evolve our wrapper accordingly.

Custom Post Types – creating a “builder” class

First step is to create a new class, which will be used for registering and configure new post types. Forget about interfaces and abstracts, let’s try something simpler:

 * Manages custom post types
 * @since 1.0.0
 * @package Ilk\Builder
 * @author WP Helpers | Carlos Matos
class Builder
     * @var string
     * Set post type params
    public $type;
    private $slug;
    private $name;
    private $singular_name;
    // ...

Simple class, with 4 properties: $slug regards to the post type slug, while $name corresponds to the plural name of the type, and $singular_name, obviously the singular. On top of it, $type indicates the query_var that index the post type (like ‘post’ for posts or ‘products’, in Woocommerce).

Basic properties done, so let’s add two methods inside the same class: a constructor and another method to register the new custom post type. From that first view, I am considering the slug and the post type name being the same, for simplifying.

     * Type constructor.
     * When class is instantiated
    public function __construct(string $singular, string $plural, string $slug)

        $this->singular_name = $singular;
        $this->name = $plural;
        $this->slug = $this->prefix . strtolower($slug);
        $this->type = $this->slug;

        // Register the post type
        add_action('init', array($this, 'register'));

     public function register() { }


The __construct() will pass the parameters to the class, setting the properties for slug, singular and plural names. Following, the function register(), which will be managing the new post type, is hooked on init.

Registering the custom post type

Class designed and set, now the best part: using the data to register your new post type. Usually, the function register_post_type(), that does the job in WordPress, forces us to set at least twenty different parameters, if considering the labels. The labels comprise whatever that labels buttons, menus and stuff for the new post type. It happens to be extremelly repetitive, so we could use the variables passed to the class to populate them all, like this:

public function register()
		$labels = array(
            'name'                  => __($this->name, $this->plugin_slug),
            'singular_name'         => __($this->singular_name, $this->plugin_slug),
            'add_new'               => __('Add New', $this->plugin_slug),
            'add_new_item'          => __('Add New ' . $this->singular_name, $this->plugin_slug),
            'edit_item'             => __('Edit ' . $this->singular_name, $this->plugin_slug),
            'new_item'              => __('New ' . $this->singular_name, $this->plugin_slug),
            'all_items'             => __('All ' . $this->name, $this->plugin_slug),
            'view_item'             => __('View ' . $this->name, $this->plugin_slug),
            'search_items'          => __('Search ' . $this->name, $this->plugin_slug),
            'not_found'             => __('No ' . strtolower($this->name) . ' found', $this->plugin_slug),
            'not_found_in_trash'    => __('No ' . strtolower($this->name) . ' found in Trash', $this->plugin_slug),
            'parent_item_colon'     => '',
            'menu_name'             => __($this->name, $this->plugin_slug)


I am using a second property – $this->plugin_slug. That means you could set an additional property to that class, passing the slug of the plugin you are developing for creating the post type, or even the theme or child theme slug. The reason for that is related to potential translations, so you want all labels to be available for being translated to other languages. However, if you want to keep it just in English, keep just the string and eliminate the second parameter for each line.

Finally, we need to add arguments to determine characteristics of the new post type. All labels will be passed together. The reason we separate them is simple: making it clearer.

	public function register()
        $args = array(
            'labels'                => $labels,
            'public'                => true,
            'publicly_queryable'    => true,
            'show_ui'               => true,
            'show_in_menu'          => $this->menu,
            'query_var'             => true,
            'rewrite'               => array( 'slug' => $this->slug ),
            'capability_type'       => 'post',
            'has_archive'           => true,
            'hierarchical'          => true,
            'menu_position'         => 8,
            'supports'              => array( 'title', 'editor', 'thumbnail'),
            'yarpp_support'         => true
        register_post_type( $this->type, $args );

Done! Now just require the class in a plugin main file or theme functions.php and test it. After requiring, all you need to do is instantiate the class with the values we set as arguments, like this.

require 'path/to/class/Builder.php';
$posttype = new Builder( 'Movie', 'Movies', 'post_movie' );

Is that all??

Yes… and no. This is pretty basic. We can endlessly sophisticate the class. Actually, we continued working on that, and put up a new library that started like this, and evolved to something way more useful. On top of creating the post types, we aimed at:

  • Set up the post type slug and the plugin slug separatedly, using other methods.
  • Add or remove ‘supports’ to the new post type.
  • Using the class object as argument in a second class – TermBuilder – and then register and associated taxonomies to our newly added post type.
  • Automate the process of determining the plural and generating the slug, so we can instatiate the class with a single argument.
  • Configure the backend menu position and visibility.
  • Automate not only the labels, but the post type messages as well.
  • Enable the post type (and its taxonomies) visibility in the rest API as a particular endpoint.

This, as we promised, will be a constant work in progress. We do not intend to abandon the library, but keep adding tools and features: adding metaboxes and setting list tables are our next targets. So follow, fork or just take a look! Cheers!

Dark Mode