Limit 1 custom post type creation for specific role

WordPress 19 posts 2 voices

  1. Hello.

    I need to limit a custom role to only be able to post one post of a specific custom post type.

    Role: Artist
    CPT: Artists

    I was trying to use Bainternet Posts Creation Limits. But it is not working.

    So I am now trying to do this with some code in my functions file. But this doesn’t work either. I would appreciate if anyone can offer advice how to make this work properly. Thank you.

    // limit artists to one profile post
    
    $user_post_count = count_user_posts( $user_ID ); // get total post for user
    if ($user_post_count > 1) { // define number 
    
    global $current_user; // get the current author
    $userid = $current_user->ID;
    $args = array(
        'post_type' => 'artists',
        'post_status' => 'publish',
        'author' => $userid
    );
    $the_posts = get_posts ( $args ); // get the published posts for that author
    $post_count = count($the_posts); // count the number of published posts for the author
    $N = 1; // set number for max posts per user
    if ($post_count > $N) {
        if (current_user_is('artist')) {   
            wp_die( 'message' ); // if the number of posts is more than N, kill the current post attempt so the author can't post
        }
    }
    
    }
  2. This shouldn’t be too hard. The ideal way to go about this is to use:

    • User meta: Store count of posts written. Either this or a small DB query to get posts.
    • Filter on map_meta_cap to prevent author from writing more than one post if their count is 1 or more.
    • Set up the CPT’s capabilities correctly and make sure the “Artist” role has the correct capabilities.

    What I need from you is to see the code for your CPT registration. I want to make sure that’s set up right to start with.

    I’m putting up for the day, but I’ll be back on in the morning. I’ll see what I can come up with.

  3. Justin,

    I am using Custom Post Type UI to create the CPT.

  4. I really wish folks wouldn’t use that plugin for CPTs. 🙂

    To properly do CPTs, you really should have a good grasp of the underlying code. But, we can probably do something. Heck, I’d be happy to write out the CPT registration code for you if that’s all you need.

    I’m going to assume that there are no custom capabilities set and that the “Capability Type” option is setting everything to the *_post and *_posts caps. You need change that option from post to artist. Then, use a plugin like Members to set up your capabilities for the post type.

    Side note: You really should name your CPT artist too. The standard naming convention is to use the singular, not the plural, version of the word.

    There’s a lot of other stuff you probably should be doing, but since you’re using that plugin, I won’t harp on that too much. 🙂

    The code

    If you make the above capability type change, I believe this code should work:

    add_filter( 'map_meta_cap', 'th_map_artist_caps', 10, 3 );
    
    function th_map_artist_caps( $caps, $cap, $user_id ) {
    
    	if ( 'create_artists' === $cap ) {
    
    		$post_count = th_get_artist_post_count( $user_id );
    
    		if ( 1 <= $post_count )
    			$caps = array( 'do_not_allow' );
    	}
    
    	return $caps;
    }
    
    function th_get_artist_post_count( $user_id ) {
    
    	$artist_posts = new WP_Query(
    		array(
    			'author'    => $user_id,
    			'post_type' => 'artists',
    			'fields'    => 'ids'
    		)
    	);
    
    	return absint( $artist_posts->found_posts );
    }
  5. Justin,

    Thank you for this offering. Per your suggestion, I have changed the CPT to the singular “artist”. I also changed the CPT capability from “post” to “artist”. I am using Members to handle capability management. I changed your code to be the singular “artist” post type (in 2 places).

    However, it seems the artist can still post more than once. What do you think?

    // limit artists to one profile post 
    
    add_filter( 'map_meta_cap', 'th_map_artist_caps', 10, 3 );
    
    function th_map_artist_caps( $caps, $cap, $user_id ) {
    
      if ( 'create_artist' === $cap ) {
    
        $post_count = th_get_artist_post_count( $user_id );
    
        if ( 1 <= $post_count )
          $caps = array( 'do_not_allow' );
      }
    
      return $caps;
    }
    
    function th_get_artist_post_count( $user_id ) {
    
      $artist_posts = new WP_Query(
        array(
          'author'    => $user_id,
          'post_type' => 'artist',
          'fields'    => 'ids'
        )
      );
    
      return absint( $artist_posts->found_posts );
    }
  6. actually I meant, I changed your code to be the singular “artist” post type and also the singular “artist” cap type line (though I tried it with “artists” as well).

    `if ( ‘create_artist’ === $cap ) {1

  7. The first thing to do is change this line of code:

    if ( 'create_artist' === $cap ) {

    Back to this:

    if ( 'create_artists' === $cap ) {
  8. That is indeed how it is in the functions file. The only thing that is different from your original code is:

    'post_type' => 'artist',

  9. If that’s not working, it could be due to the plugin you have. It’s hard to say. create_posts (i.e., create_artists for your CPT) is relatively new, so the plugin might not be handling it correctly.

    That’s why it’s so much better to code this up on your own. We’d have direct control over everything. Plus, I could actually test code on my end.

    If you want to go that route, I’d be happy to help. I even have a starter file here: https://gist.github.com/justintadlock/6552000 It needs to be updated for the new REST API stuff, but you can pretty much just copy/paste the code in a custom plugin and change the fields you want.

  10. Yep, I’m looking through the CPT UI plugin code. It doesn’t set the capabilities under the hood. It only sets the capability_type.

    The reason this is not good is because if there’s no create_posts cap set, then it’ll automatically fallback to whatever your edit_posts cap is. In this case, that cap would be edit_artists.

    However, we don’t want to block the edit_artists cap because that would mean blocking users from editing altogether.

    At this point, I can’t think of any other way around this. You need the create_posts cap set to be able to target post creation. This means you need to code the CPT registration on your own or ask the CPT UI plugin author to update their plugin to allow you to add in custom capabilities.

  11. I am into the idea of replacing CPTUI with custom code, but I have two custom post types that I am working with. So double the work.

    I checked out your link. It is a bit overwhelming, but I get the gist of it. You use the word example(s) throughout the document for has_archive, query_var, capabilities, etc. Would these all be changed to the same name, in our case: artist(s)? (Which is also the name of a custom role.) Also, how to I handle the “example-textdomain” lines?

  12. example could be replaced with artist and examples with artists where appropriate.

    As for example-textdomain, that’s just for translations. I wrote the Gist for people building public plugins. So, you might see something like this:

    __( 'Some text', 'example-textdomain' )

    That can be replaced with:

    'Some text'

    That’s if you’re not worried about translating into another language.

  13. Justin,

    I edited the code like below and added it to my functions file. However, the CPT did not show up on the menu. I am sure I did something wrong here.

    # Register custom post types on the 'init' hook.
    add_action( 'init', 'my_register_post_types' );
    
    /**
     * Registers post types needed by the plugin.
     *
     * @since  1.0.0
     * @access public
     * @return void
     */
    function my_register_post_types() {
    
    	// Set up the arguments for the post type.
    	$args = array(
    
    		// A short description of what your post type is. As far as I know, this isn't used anywhere
    		// in core WordPress.  However, themes may choose to display this on post type archives.
    		'description'         => __( 'This is a description for my post type.', '2artist' ), // string
    
    		// Whether the post type should be used publicly via the admin or by front-end users.  This
    		// argument is sort of a catchall for many of the following arguments.  I would focus more
    		// on adjusting them to your liking than this argument.
    		'public'              => true, // bool (default is FALSE)
    
    		// Whether queries can be performed on the front end as part of parse_request().
    		'publicly_queryable'  => true, // bool (defaults to 'public').
    
    		// Whether to exclude posts with this post type from front end search results.
    		'exclude_from_search' => false, // bool (defaults to the opposite of 'public' argument)
    
    		// Whether individual post type items are available for selection in navigation menus.
    		'show_in_nav_menus'   => false, // bool (defaults to 'public')
    
    		// Whether to generate a default UI for managing this post type in the admin. You'll have
    		// more control over what's shown in the admin with the other arguments.  To build your
    		// own UI, set this to FALSE.
    		'show_ui'             => true, // bool (defaults to 'public')
    
    		// Whether to show post type in the admin menu. 'show_ui' must be true for this to work.
    		// Can also set this to a string of a top-level menu (e.g., 'tools.php'), which will make
    		// the post type screen be a sub-menu.
    		'show_in_menu'        => true, // bool (defaults to 'show_ui')
    
    		// Whether to make this post type available in the WordPress admin bar. The admin bar adds
    		// a link to add a new post type item.
    		'show_in_admin_bar'   => true, // bool (defaults to 'show_in_menu')
    
    		// The position in the menu order the post type should appear. 'show_in_menu' must be true
    		'menu_position'       => null, // int (defaults to 25 - below comments)
    
    		// The URI to the icon to use for the admin menu item or a dashicon class. See:
    		// https://developer.wordpress.org/resource/dashicons/
    		'menu_icon'           => null, // string (defaults to use the post icon)
    
    		// Whether the posts of this post type can be exported via the WordPress import/export plugin
    		// or a similar plugin.
    		'can_export'          => true, // bool (defaults to TRUE)
    
    		// Whether to delete posts of this type when deleting a user who has written posts.
    		'delete_with_user'    => false, // bool (defaults to TRUE if the post type supports 'author')
    
    		// Whether this post type should allow hierarchical (parent/child/grandchild/etc.) posts.
    		'hierarchical'        => false, // bool (defaults to FALSE)
    
    		// Whether the post type has an index/archive/root page like the "page for posts" for regular
    		// posts. If set to TRUE, the post type name will be used for the archive slug.  You can also
    		// set this to a string to control the exact name of the archive slug.
    		'has_archive'         => '2artists', // bool|string (defaults to FALSE)
    
    		// Sets the query_var key for this post type. If set to TRUE, the post type name will be used.
    		// You can also set this to a custom string to control the exact key.
    		'query_var'           => '2artist', // bool|string (defaults to TRUE - post type name)
    
    		// A string used to build the edit, delete, and read capabilities for posts of this type. You
    		// can use a string or an array (for singular and plural forms).  The array is useful if the
    		// plural form can't be made by simply adding an 's' to the end of the word.  For example,
    		// array( 'box', 'boxes' ).
    		'capability_type'     => '2artist', // string|array (defaults to 'post')
    
    		// Whether WordPress should map the meta capabilities (edit_post, read_post, delete_post) for
    		// you.  If set to FALSE, you'll need to roll your own handling of this by filtering the
    		// 'map_meta_cap' hook.
    		'map_meta_cap'        => true, // bool (defaults to FALSE)
    
    		// Provides more precise control over the capabilities than the defaults.  By default, WordPress
    		// will use the 'capability_type' argument to build these capabilities.  More often than not,
    		// this results in many extra capabilities that you probably don't need.  The following is how
    		// I set up capabilities for many post types, which only uses three basic capabilities you need
    		// to assign to roles: 'manage_examples', 'edit_examples', 'create_examples'.  Each post type
    		// is unique though, so you'll want to adjust it to fit your needs.
    		'capabilities' => array(
    
    			// meta caps (don't assign these to roles)
    			'edit_post'              => 'edit_2artist',
    			'read_post'              => 'read_2artist',
    			'delete_post'            => 'delete_2artist',
    
    			// primitive/meta caps
    			'create_posts'           => 'create_2artists',
    
    			// primitive caps used outside of map_meta_cap()
    			'edit_posts'             => 'edit_2artists',
    			'edit_others_posts'      => 'manage_2artists',
    			'publish_posts'          => 'manage_2artists',
    			'read_private_posts'     => 'read',
    
    			// primitive caps used inside of map_meta_cap()
    			'read'                   => 'read',
    			'delete_posts'           => 'manage_2artists',
    			'delete_private_posts'   => 'manage_2artists',
    			'delete_published_posts' => 'manage_2artists',
    			'delete_others_posts'    => 'manage_2artists',
    			'edit_private_posts'     => 'edit_2artists',
    			'edit_published_posts'   => 'edit_2artists'
    		),
    
    		// How the URL structure should be handled with this post type.  You can set this to an
    		// array of specific arguments or true|false.  If set to FALSE, it will prevent rewrite
    		// rules from being created.
    		'rewrite' => array(
    
    			// The slug to use for individual posts of this type.
    			'slug'       => '2artist', // string (defaults to the post type name)
    
    			// Whether to show the $wp_rewrite->front slug in the permalink.
    			'with_front' => false, // bool (defaults to TRUE)
    
    			// Whether to allow single post pagination via the <!--nextpage--> quicktag.
    			'pages'      => true, // bool (defaults to TRUE)
    
    			// Whether to create pretty permalinks for feeds.
    			'feeds'      => true, // bool (defaults to the 'has_archive' argument)
    
    			// Assign an endpoint mask to this permalink.
    			'ep_mask'    => EP_PERMALINK, // const (defaults to EP_PERMALINK)
    		),
    
    		// What WordPress features the post type supports.  Many arguments are strictly useful on
    		// the edit post screen in the admin.  However, this will help other themes and plugins
    		// decide what to do in certain situations.  You can pass an array of specific features or
    		// set it to FALSE to prevent any features from being added.  You can use
    		// add_post_type_support() to add features or remove_post_type_support() to remove features
    		// later.  The default features are 'title' and 'editor'.
    		'supports' => array(
    
    			// Post titles ($post->post_title).
    			'title',
    
    			// Post content ($post->post_content).
    			'editor',
    
    			// Post excerpt ($post->post_excerpt).
    			'excerpt',
    
    			// Post author ($post->post_author).
    			'author',
    
    			// Featured images (the user's theme must support 'post-thumbnails').
    			'thumbnail',
    
    			// Displays comments meta box.  If set, comments (any type) are allowed for the post.
    			'comments',
    
    			// Displays meta box to send trackbacks from the edit post screen.
    			'trackbacks',
    
    			// Displays the Custom Fields meta box. Post meta is supported regardless.
    			'custom-fields',
    
    			// Displays the Revisions meta box. If set, stores post revisions in the database.
    			'revisions',
    
    			// Displays the Attributes meta box with a parent selector and menu_order input box.
    			'page-attributes',
    
    			// Displays the Format meta box and allows post formats to be used with the posts.
    			'post-formats',
    		),
    
    		// Labels used when displaying the posts in the admin and sometimes on the front end.  These
    		// labels do not cover post updated, error, and related messages.  You'll need to filter the
    		// 'post_updated_messages' hook to customize those.
    		'labels' => array(
    			'name'                  => __( 'Posts',                   '2artist' ),
    			'singular_name'         => __( 'Post',                    '2artist' ),
    			'menu_name'             => __( 'Posts',                   '2artist' ),
    			'name_admin_bar'        => __( 'Posts',                   '2artist' ),
    			'add_new'               => __( 'Add New',                 '2artist' ),
    			'add_new_item'          => __( 'Add New Post',            '2artist' ),
    			'edit_item'             => __( 'Edit Post',               '2artist' ),
    			'new_item'              => __( 'New Post',                '2artist' ),
    			'view_item'             => __( 'View Post',               '2artist' ),
    			'search_items'          => __( 'Search Posts',            '2artist' ),
    			'not_found'             => __( 'No posts found',          '2artist' ),
    			'not_found_in_trash'    => __( 'No posts found in trash', '2artist' ),
    			'all_items'             => __( 'All Posts',               '2artist' ),
    			'featured_image'        => __( 'Featured Image',          '2artist' ),
    			'set_featured_image'    => __( 'Set featured image',      '2artist' ),
    			'remove_featured_image' => __( 'Remove featured image',   '2artist' ),
    			'use_featured_image'    => __( 'Use as featred image',    '2artist' ),
    			'insert_into_item'      => __( 'Insert into post',        '2artist' ),
    			'uploaded_to_this_item' => __( 'Uploaded to this post',   '2artist' ),
    			'views'                 => __( 'Filter posts list',       '2artist' ),
    			'pagination'            => __( 'Posts list navigation',   '2artist' ),
    			'list'                  => __( 'Posts list',              '2artist' ),
    
    			// Labels for hierarchical post types only.
    			'parent_item'        => __( 'Parent Post',                '2artist' ),
    			'parent_item_colon'  => __( 'Parent Post:',               '2artist' ),
    		)
    	);
    
    	// Register the post type.
    	register_post_type(
    		'2artist', // Post type name. Max of 20 characters. Uppercase and spaces not allowed.
    		$args      // Arguments for post type.
    	);
    }

    To test if I could just make a CPT successfully, I used the code below and was indeed successful. Why didn’t I get your code to work?

    function codex_2artist_init() {
      $labels = array(
        'name'               => _x('2artists', 'post type general name', 'your-plugin-textdomain'),
        'singular_name'      => _x('2artist', 'post type singular name', 'your-plugin-textdomain'),
        'menu_name'          => _x('2artists', 'admin menu', 'your-plugin-textdomain'),
        'name_admin_bar'     => _x('2artist', 'add new on admin bar', 'your-plugin-textdomain'),
        'add_new'            => _x('Add New', '2artist', 'your-plugin-textdomain'),
        'add_new_item'       => __('Add New 2artist', 'your-plugin-textdomain'),
        'new_item'           => __('New 2artist', 'your-plugin-textdomain'),
        'edit_item'          => __('Edit 2artist', 'your-plugin-textdomain'),
        'view_item'          => __('View 2artist', 'your-plugin-textdomain'),
        'all_items'          => __('All 2artists', 'your-plugin-textdomain'),
        'search_items'       => __('Search 2artists', 'your-plugin-textdomain'),
        'parent_item_colon'  => __('Parent 2artists:', 'your-plugin-textdomain'),
        'not_found'          => __('No 2artists found.', 'your-plugin-textdomain'),
        'not_found_in_trash' => __('No 2artists found in Trash.', 'your-plugin-textdomain'),
      );
      $args = array(
        'labels'             => $labels,
        'taxonomies'         => array('category'),
        'public'             => true,
        'publicly_queryable' => true,
        'show_ui'            => true,
        'show_in_menu'       => true,
        'query_var'          => true,
        'rewrite'            => array('slug' => '2artist'),
        'map_meta_cap'        => true, 
        'has_archive'        => true,
        'hierarchical'       => false,
        'menu_position'      => null,
        'supports'           => array('title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments', 'custom-fields')
      );
      register_post_type('2artist', $args);
    }
    add_action('init', 'codex_2artist_init');
  14. Justin,

    I edited the code like below and added it to my functions file. However, the CPT did not show up on the menu. I am sure I did something wrong here.

    # Register custom post types on the 'init' hook.
    add_action( 'init', 'my_register_post_types' );
    
    /**
     * Registers post types needed by the plugin.
     *
     * @since  1.0.0
     * @access public
     * @return void
     */
    function my_register_post_types() {
    
    	// Set up the arguments for the post type.
    	$args = array(
    
    		// A short description of what your post type is. As far as I know, this isn't used anywhere
    		// in core WordPress.  However, themes may choose to display this on post type archives.
    		'description'         => __( 'This is a description for my post type.', '2artist' ), // string
    
    		// Whether the post type should be used publicly via the admin or by front-end users.  This
    		// argument is sort of a catchall for many of the following arguments.  I would focus more
    		// on adjusting them to your liking than this argument.
    		'public'              => true, // bool (default is FALSE)
    
    		// Whether queries can be performed on the front end as part of parse_request().
    		'publicly_queryable'  => true, // bool (defaults to 'public').
    
    		// Whether to exclude posts with this post type from front end search results.
    		'exclude_from_search' => false, // bool (defaults to the opposite of 'public' argument)
    
    		// Whether individual post type items are available for selection in navigation menus.
    		'show_in_nav_menus'   => false, // bool (defaults to 'public')
    
    		// Whether to generate a default UI for managing this post type in the admin. You'll have
    		// more control over what's shown in the admin with the other arguments.  To build your
    		// own UI, set this to FALSE.
    		'show_ui'             => true, // bool (defaults to 'public')
    
    		// Whether to show post type in the admin menu. 'show_ui' must be true for this to work.
    		// Can also set this to a string of a top-level menu (e.g., 'tools.php'), which will make
    		// the post type screen be a sub-menu.
    		'show_in_menu'        => true, // bool (defaults to 'show_ui')
    
    		// Whether to make this post type available in the WordPress admin bar. The admin bar adds
    		// a link to add a new post type item.
    		'show_in_admin_bar'   => true, // bool (defaults to 'show_in_menu')
    
    		// The position in the menu order the post type should appear. 'show_in_menu' must be true
    		'menu_position'       => null, // int (defaults to 25 - below comments)
    
    		// The URI to the icon to use for the admin menu item or a dashicon class. See:
    		// https://developer.wordpress.org/resource/dashicons/
    		'menu_icon'           => null, // string (defaults to use the post icon)
    
    		// Whether the posts of this post type can be exported via the WordPress import/export plugin
    		// or a similar plugin.
    		'can_export'          => true, // bool (defaults to TRUE)
    
    		// Whether to delete posts of this type when deleting a user who has written posts.
    		'delete_with_user'    => false, // bool (defaults to TRUE if the post type supports 'author')
    
    		// Whether this post type should allow hierarchical (parent/child/grandchild/etc.) posts.
    		'hierarchical'        => false, // bool (defaults to FALSE)
    
    		// Whether the post type has an index/archive/root page like the "page for posts" for regular
    		// posts. If set to TRUE, the post type name will be used for the archive slug.  You can also
    		// set this to a string to control the exact name of the archive slug.
    		'has_archive'         => '2artists', // bool|string (defaults to FALSE)
    
    		// Sets the query_var key for this post type. If set to TRUE, the post type name will be used.
    		// You can also set this to a custom string to control the exact key.
    		'query_var'           => '2artist', // bool|string (defaults to TRUE - post type name)
    
    		// A string used to build the edit, delete, and read capabilities for posts of this type. You
    		// can use a string or an array (for singular and plural forms).  The array is useful if the
    		// plural form can't be made by simply adding an 's' to the end of the word.  For example,
    		// array( 'box', 'boxes' ).
    		'capability_type'     => '2artist', // string|array (defaults to 'post')
    
    		// Whether WordPress should map the meta capabilities (edit_post, read_post, delete_post) for
    		// you.  If set to FALSE, you'll need to roll your own handling of this by filtering the
    		// 'map_meta_cap' hook.
    		'map_meta_cap'        => true, // bool (defaults to FALSE)
    
    		// Provides more precise control over the capabilities than the defaults.  By default, WordPress
    		// will use the 'capability_type' argument to build these capabilities.  More often than not,
    		// this results in many extra capabilities that you probably don't need.  The following is how
    		// I set up capabilities for many post types, which only uses three basic capabilities you need
    		// to assign to roles: 'manage_examples', 'edit_examples', 'create_examples'.  Each post type
    		// is unique though, so you'll want to adjust it to fit your needs.
    		'capabilities' => array(
    
    			// meta caps (don't assign these to roles)
    			'edit_post'              => 'edit_2artist',
    			'read_post'              => 'read_2artist',
    			'delete_post'            => 'delete_2artist',
    
    			// primitive/meta caps
    			'create_posts'           => 'create_2artists',
    
    			// primitive caps used outside of map_meta_cap()
    			'edit_posts'             => 'edit_2artists',
    			'edit_others_posts'      => 'manage_2artists',
    			'publish_posts'          => 'manage_2artists',
    			'read_private_posts'     => 'read',
    
    			// primitive caps used inside of map_meta_cap()
    			'read'                   => 'read',
    			'delete_posts'           => 'manage_2artists',
    			'delete_private_posts'   => 'manage_2artists',
    			'delete_published_posts' => 'manage_2artists',
    			'delete_others_posts'    => 'manage_2artists',
    			'edit_private_posts'     => 'edit_2artists',
    			'edit_published_posts'   => 'edit_2artists'
    		),
    
    		// How the URL structure should be handled with this post type.  You can set this to an
    		// array of specific arguments or true|false.  If set to FALSE, it will prevent rewrite
    		// rules from being created.
    		'rewrite' => array(
    
    			// The slug to use for individual posts of this type.
    			'slug'       => '2artist', // string (defaults to the post type name)
    
    			// Whether to show the $wp_rewrite->front slug in the permalink.
    			'with_front' => false, // bool (defaults to TRUE)
    
    			// Whether to allow single post pagination via the <!--nextpage--> quicktag.
    			'pages'      => true, // bool (defaults to TRUE)
    
    			// Whether to create pretty permalinks for feeds.
    			'feeds'      => true, // bool (defaults to the 'has_archive' argument)
    
    			// Assign an endpoint mask to this permalink.
    			'ep_mask'    => EP_PERMALINK, // const (defaults to EP_PERMALINK)
    		),
    
    		// What WordPress features the post type supports.  Many arguments are strictly useful on
    		// the edit post screen in the admin.  However, this will help other themes and plugins
    		// decide what to do in certain situations.  You can pass an array of specific features or
    		// set it to FALSE to prevent any features from being added.  You can use
    		// add_post_type_support() to add features or remove_post_type_support() to remove features
    		// later.  The default features are 'title' and 'editor'.
    		'supports' => array(
    
    			// Post titles ($post->post_title).
    			'title',
    
    			// Post content ($post->post_content).
    			'editor',
    
    			// Post excerpt ($post->post_excerpt).
    			'excerpt',
    
    			// Post author ($post->post_author).
    			'author',
    
    			// Featured images (the user's theme must support 'post-thumbnails').
    			'thumbnail',
    
    			// Displays comments meta box.  If set, comments (any type) are allowed for the post.
    			'comments',
    
    			// Displays meta box to send trackbacks from the edit post screen.
    			'trackbacks',
    
    			// Displays the Custom Fields meta box. Post meta is supported regardless.
    			'custom-fields',
    
    			// Displays the Revisions meta box. If set, stores post revisions in the database.
    			'revisions',
    
    			// Displays the Attributes meta box with a parent selector and menu_order input box.
    			'page-attributes',
    
    			// Displays the Format meta box and allows post formats to be used with the posts.
    			'post-formats',
    		),
    
    		// Labels used when displaying the posts in the admin and sometimes on the front end.  These
    		// labels do not cover post updated, error, and related messages.  You'll need to filter the
    		// 'post_updated_messages' hook to customize those.
    		'labels' => array(
    			'name'                  => __( 'Posts',                   '2artist' ),
    			'singular_name'         => __( 'Post',                    '2artist' ),
    			'menu_name'             => __( 'Posts',                   '2artist' ),
    			'name_admin_bar'        => __( 'Posts',                   '2artist' ),
    			'add_new'               => __( 'Add New',                 '2artist' ),
    			'add_new_item'          => __( 'Add New Post',            '2artist' ),
    			'edit_item'             => __( 'Edit Post',               '2artist' ),
    			'new_item'              => __( 'New Post',                '2artist' ),
    			'view_item'             => __( 'View Post',               '2artist' ),
    			'search_items'          => __( 'Search Posts',            '2artist' ),
    			'not_found'             => __( 'No posts found',          '2artist' ),
    			'not_found_in_trash'    => __( 'No posts found in trash', '2artist' ),
    			'all_items'             => __( 'All Posts',               '2artist' ),
    			'featured_image'        => __( 'Featured Image',          '2artist' ),
    			'set_featured_image'    => __( 'Set featured image',      '2artist' ),
    			'remove_featured_image' => __( 'Remove featured image',   '2artist' ),
    			'use_featured_image'    => __( 'Use as featred image',    '2artist' ),
    			'insert_into_item'      => __( 'Insert into post',        '2artist' ),
    			'uploaded_to_this_item' => __( 'Uploaded to this post',   '2artist' ),
    			'views'                 => __( 'Filter posts list',       '2artist' ),
    			'pagination'            => __( 'Posts list navigation',   '2artist' ),
    			'list'                  => __( 'Posts list',              '2artist' ),
    
    			// Labels for hierarchical post types only.
    			'parent_item'        => __( 'Parent Post',                '2artist' ),
    			'parent_item_colon'  => __( 'Parent Post:',               '2artist' ),
    		)
    	);
    
    	// Register the post type.
    	register_post_type(
    		'2artist', // Post type name. Max of 20 characters. Uppercase and spaces not allowed.
    		$args      // Arguments for post type.
    	);
    }

    To test if I could just make a CPT successfully, I used the code below and was indeed successful. Why didn’t I get your code to work?

    function codex_2artist_init() {
      $labels = array(
        'name'               => _x('2artists', 'post type general name', 'your-plugin-textdomain'),
        'singular_name'      => _x('2artist', 'post type singular name', 'your-plugin-textdomain'),
        'menu_name'          => _x('2artists', 'admin menu', 'your-plugin-textdomain'),
        'name_admin_bar'     => _x('2artist', 'add new on admin bar', 'your-plugin-textdomain'),
        'add_new'            => _x('Add New', '2artist', 'your-plugin-textdomain'),
        'add_new_item'       => __('Add New 2artist', 'your-plugin-textdomain'),
        'new_item'           => __('New 2artist', 'your-plugin-textdomain'),
        'edit_item'          => __('Edit 2artist', 'your-plugin-textdomain'),
        'view_item'          => __('View 2artist', 'your-plugin-textdomain'),
        'all_items'          => __('All 2artists', 'your-plugin-textdomain'),
        'search_items'       => __('Search 2artists', 'your-plugin-textdomain'),
        'parent_item_colon'  => __('Parent 2artists:', 'your-plugin-textdomain'),
        'not_found'          => __('No 2artists found.', 'your-plugin-textdomain'),
        'not_found_in_trash' => __('No 2artists found in Trash.', 'your-plugin-textdomain'),
      );
      $args = array(
        'labels'             => $labels,
        'taxonomies'         => array('category'),
        'public'             => true,
        'publicly_queryable' => true,
        'show_ui'            => true,
        'show_in_menu'       => true,
        'query_var'          => true,
        'rewrite'            => array('slug' => '2artist'),
        'map_meta_cap'        => true, 
        'has_archive'        => true,
        'hierarchical'       => false,
        'menu_position'      => null,
        'supports'           => array('title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments', 'custom-fields')
      );
      register_post_type('2artist', $args);
    }
    add_action('init', 'codex_2artist_init');
  15. Here’s a full working copy of the code. Just make sure you go to Users > Roles (using the Members plugin) and assign the necessary capabilities for your role.

    # Register custom artist types on the 'init' hook.
    add_action( 'init', 'my_register_artist_types' );
    
    /**
     * Registers artist types needed by the plugin.
     *
     * @since  1.0.0
     * @access public
     * @return void
     */
    function my_register_artist_types() {
    
    	// Set up the arguments for the artist type.
    	$args = array(
    		'description'         => __( 'This is a description for my artist type.', 'example' ),
    		'public'              => true,
    		'publicly_queryable'  => true,
    		'exclude_from_search' => false,
    		'show_in_nav_menus'   => false,
    		'show_ui'             => true,
    		'show_in_menu'        => true,
    		'show_in_admin_bar'   => true,
    		'menu_position'       => null,
    		'menu_icon'           => null,
    		'can_export'          => true,
    		'delete_with_user'    => false,
    		'hierarchical'        => false,
    		'has_archive'         => 'artists',
    		'query_var'           => 'artist',
    		'capability_type'     => 'artist',
    		'map_meta_cap'        => true,
    		'capabilities' => array(
    
    			// meta caps (don't assign these to roles)
    			'edit_post'              => 'edit_artist',
    			'read_post'              => 'read_artist',
    			'delete_post'            => 'delete_artist',
    
    			// primitive/meta caps
    			'create_posts'           => 'create_artists',
    
    			// primitive caps used outside of map_meta_cap()
    			'edit_posts'             => 'edit_artists',
    			'edit_others_posts'      => 'edit_others_artists',
    			'publish_posts'          => 'publish_artists',
    			'read_private_posts'     => 'read_private_artists',
    
    			// primitive caps used inside of map_meta_cap()
    			'read'                   => 'read',
    			'delete_posts'           => 'delete_artists',
    			'delete_private_posts'   => 'delete_private_artists',
    			'delete_published_posts' => 'delete_published_artists',
    			'delete_others_posts'    => 'delete_others_artists',
    			'edit_private_posts'     => 'edit_private_artists',
    			'edit_published_posts'   => 'edit_published_artists'
    		),
    		'rewrite' => array(
    			'slug'       => 'artist',
    			'with_front' => false,
    			'pages'      => true,
    			'feeds'      => true,
    			'ep_mask'    => EP_PERMALINK,
    		),
    		'supports' => array(
    			'title',
    			'editor',
    			'excerpt',
    			'author',
    			'thumbnail',
    			'comments',
    			'trackbacks',
    			'custom-fields',
    			'revisions',
    			'page-attributes',
    			'artist-formats',
    		),
    		'labels' => array(
    			'name'                  => __( 'Artists',                   'example' ),
    			'singular_name'         => __( 'Artist',                    'example' ),
    			'menu_name'             => __( 'Artists',                   'example' ),
    			'name_admin_bar'        => __( 'Artists',                   'example' ),
    			'add_new'               => __( 'Add New',                   'example' ),
    			'add_new_item'          => __( 'Add New Artist',            'example' ),
    			'edit_item'             => __( 'Edit Artist',               'example' ),
    			'new_item'              => __( 'New Artist',                'example' ),
    			'view_item'             => __( 'View Artist',               'example' ),
    			'search_items'          => __( 'Search Artists',            'example' ),
    			'not_found'             => __( 'No artists found',          'example' ),
    			'not_found_in_trash'    => __( 'No artists found in trash', 'example' ),
    			'all_items'             => __( 'All Artists',               'example' ),
    			'featured_image'        => __( 'Featured Image',            'example' ),
    			'set_featured_image'    => __( 'Set featured image',        'example' ),
    			'remove_featured_image' => __( 'Remove featured image',     'example' ),
    			'use_featured_image'    => __( 'Use as featred image',      'example' ),
    			'insert_into_item'      => __( 'Insert into artist',        'example' ),
    			'uploaded_to_this_item' => __( 'Uploaded to this artist',   'example' ),
    			'views'                 => __( 'Filter artists list',       'example' ),
    			'pagination'            => __( 'Artists list navigation',   'example' ),
    			'list'                  => __( 'Artists list',              'example' ),
    		)
    	);
    
    	// Register the artist type.
    	register_post_type( 'artist', $args );
    }