I am looking for a good approach regarding the creation of custom post types in WordPress, using object orientation. How can I create CPTs in an clean, organized way?

Take into consideration these 3 scenarios:

  1. Plugin for distribution (5.2 compatible)
  2. Theme for distribution
  3. Own project, with total control over the PHP version and dependencies

I'll answer scenario #3, even though it can be adapted for scenarios #1 and #2, too.

WordPress codex recommends that we create Custom Post Types (CPTs) in a plugin. Following that recommendation, I will write my answer using modern PHP, which is not meant for a distributed plugin, but rather for own projects where you have control over the PHP version running.

In the example code bellow, I will use raw examples just to give an idea of the architecture behind the creation of CPTs in a OOP manner, but in a real project, "my-plugin" would be something like "my-project", and the creation of CPTs would be a sort of module of that plugin.


* Plugin name: My Custom Post Types
namespace MyPlugin;

use MyPlugin/CPT/Car;

// ... Composer autoload or similar here




namespace MyPlugin\CPT;

 * Class Car
 * Handles the creation of a "Car" custom post type
class Car
    public static function register()
        $instance = new self;

        add_action('init', [$instance, 'registerPostType']);
        add_action('add_meta_boxes', [$instance, 'registerMetaboxes']);

        // Do something else related to "Car" post type

    public function registerPostType()
        register_post_type( 'car', [
            'label' => 'Cars',

    public function registerMetaBoxes()
        // Color metabox
            __('Color', 'my-plugin'),
            [$this, 'colorMetabox'],

    public function colorMetabox()
        echo 'Foo';

This way we have a namespace for our CPTs, and an object where we can manage it's properties, such as registering the post type, adding or removing metaboxes, etc.

If we are accepting insertion of "Cars" from the frontend, we would use the REST API to receive the POST request and handle it in a REST Controller Class, but that is out of the scope of this answer.

  • Namespaces aren't compatible with PHP 5.2. – Jacob Peattie Dec 23 '18 at 1:07
  • Hi Jacob, read the first sentence of the answer again, please. – Lucas Bustamante Dec 23 '18 at 1:08

I typically use something like this:


Plugin Name: My Plugin
Plugin URI: https://my.plugin.com
Description: My plugin description
Version: 1.0.0
Author: Me
Author URI: https://my.website.com
License: GPL2 (or whatever)


class myClass {

    function __construct() {

        add_action( 'init', array( $this, 'create_post_type' ) );


    function create_post_type() {

        $name = 'Foos';
        $singular_name = 'Foo';
            'rg_' . strtolower( $name ),
                'labels' => array(
                    'name'               => _x( $name, 'post type general name' ),
                    'singular_name'      => _x( $singular_name, 'post type singular name'),
                    'menu_name'          => _x( $name, 'admin menu' ),
                    'name_admin_bar'     => _x( $singular_name, 'add new on admin bar' ),
                    'add_new'            => _x( 'Add New', strtolower( $name ) ),
                    'add_new_item'       => __( 'Add New ' . $singular_name ),
                    'new_item'           => __( 'New ' . $singular_name ),
                    'edit_item'          => __( 'Edit ' . $singular_name ),
                    'view_item'          => __( 'View ' . $singular_name ),
                    'all_items'          => __( 'All ' . $name ),
                    'search_items'       => __( 'Search ' . $name ),
                    'parent_item_colon'  => __( 'Parent :' . $name ),
                    'not_found'          => __( 'No ' . strtolower( $name ) . ' found.'),
                    'not_found_in_trash' => __( 'No ' . strtolower( $name ) . ' found in Trash.' )
                'public'             => true,
                'has_archive'        => strtolower($taxonomy_name),
                'hierarchical'       => false,
                'rewrite'            => array( 'slug' => $name ),
                'menu_icon'          => 'dashicons-carrot'



$my_class = new myClass();


You can call the add_action function to another function too, as long as the action is part of the init callbacks.

Note how the add_action function uses an array instead of a string when it is placed inside a class.

You can just use this as the main file in your plugin if you want.

The codex doesn't show using the variables $name and $singular_name, but i like to start this way so that I can easily change it whenever I want during development.

Here is more information about creating post types: https://developer.wordpress.org/reference/functions/register_post_type/ https://codex.wordpress.org/Function_Reference/register_post_type

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.