Workflow 1.0: creating the CPTs and roles

Here’s where we dive into the technical details and the code. The first step in creating our workflow is setting up a custom post type for each office, and using `map_meta_cap` to create new capabilities.

Register a post type

First, we register one of the custom post types. Create your own custom plugin and follow along.

/* Plugin Name: Workflow 1 */
add_action( 'init', 'workflow_1_create_cpts' );
function workflow_1_create_cpts() {
	/* Labels are pretty standard stuff. You don't necessarily have to include all of these, but we wanted to make sure each policy type used a label of an appropriate length. (For example, the menu_name is just Academic Affairs so that in wp-admin the label fits all on one line.) */
	$labels = array(
		'name'               => 'Academic Affairs',
		'singular_name'      => 'Academic Affairs Policy',
		'menu_name'          => 'Academic Affairs',
		'name_admin_bar'     => 'Academic Affairs Policy',
		'add_new'            => 'Add New',
		'add_new_item'       => 'Add New Policy',
		'new_item'           => 'New Policy',
		'edit_item'          => 'Edit Policy',
		'view_item'          => 'View Policy',
		'all_items'          => 'All Policies',
		'search_items'       => 'Search Policies',
		'not_found'          => 'No policies found.',
		'not_found_in_trash' => 'No policies found in trash.',
	/* Here's the really important part. By defining this capabilities array, we are creating brand-new capabilities. This is how we can allow a user to edit only a specific CPT, and not other CPTs. Be careful - there are several that should be singular (edit_aap) and the rest that should be plural (create_aaps). */
	$capabilities = array(
		'edit_post'          => 'edit_aap',
		'read_post'          => 'read_aap',
		'delete_post'        => 'delete_aap',
		'create_posts'      		=> 'create_aaps',
		'delete_posts'				=> 'delete_aaps',
		'delete_others_posts'		=> 'delete_others_aaps',
		'delete_private_posts'		=> 'delete_private_aaps',
		'delete_published_posts'	=> 'delete_published_aaps',
		'edit_posts'				=> 'edit_aaps',
		'edit_others_posts'			=> 'edit_others_aaps',
		'edit_private_posts'		=> 'edit_private_aaps',
		'edit_published_posts'		=> 'edit_published_aaps',
		'publish_posts'				=> 'publish_aaps',
		'read_private_posts'		=> 'read_private_aaps',
	/* Finally, we wrap everything up into one big argument array. The most important part here is "map_meta_cap" - that's what will tell WordPress to use the $capabilities array rather than built-in Core capabilities. (Another important bit is the support for revisions. Without them, it wouldn't be possible to have one published version and another pending copy.) */
	$args = array(
		'show_in_rest' => true,
		'map_meta_cap' => true,
		'menu_icon' => 'dashicons-book',
		'menu_position' => 1.01,
		'public' => true,
		'labels'  => $labels,
		'has_archive' => true,
		'hierarchical' => false,
		'supports' => array('title', 'editor', 'author', 'revisions', 'page-attributes'),
		'capabilities' => $capabilities,
		'rewrite' => array('slug' => 'academic-affairs'),
	register_post_type( 'aap', $args );

Lather, rinse, and repeat for as many post types as you need. Oh, and I should mention a common gotcha at this point: the name of a capability (such as delete_others_aaps) has a maximum character limit. I initially tried to spell them all out for clarity – like delete_others_academic_affairs_posts – and it took a long time to track down this obscure limit and realize I’d have to abbreviate. I hope that saves someone else some time and trouble. You still have to come up with unique capabilities so you sandbox your users, so don’t make them so short that you later use the same ones for another custom post type.

Create and update roles

At this point, if you activate your plugin, you may be surprised that your shiny new post type does not appear in your wp-admin menu. It’s another common gotcha: once you map custom meta capabilities, you’ve actually created capabilities that an Administrator does not have. So first, if you want Administrators to have access to your custom post type, you have to explicitly say so.

Adding onto your plugin file:

register_activation_hook(__FILE__, 'workflow_1_add_roles');
function workflow_1_add_roles() {
	/* Grab the Administrator role */
	$role = get_role('administrator');
	/* Add each capability you want them to have. This is the full list, but you might not want to give your Administrators any or all of these capabilities. */

Deactivate and reactivate your plugin, and you’ll see “Academic Affairs” appear as a post type in wp-admin. (If you don’t see it, something went wrong, so go back and scour for typos.)

Next, you can add your custom roles. You can call these anything you want, of course, but we’ll stick with the Policy Library scenario. Once again, keep an eye on how long your role names get – you can hit a character limit, so make sure they’re unique and easy to identify, but not too long.

Interestingly, the next call – “add_role” – doesn’t need a hook to fire. It will run on every wp-admin request. So, you can set these up once, load an admin page to make sure they run, and then comment out your code. (It’s worth commenting out instead of deleting so you can have something to refer back to later, in case there was a typo or someone has questions about the roles or capabilities.)

Here’s the code to create a Policy Editor of Academic Affairs.

/* In addition to editing rights, Policy Editors can see and edit widgets on the dashboard; they can read all published content; and they can upload files. */
add_role('policy_editor_aap', 'Editor Academic Affairs', array(
	'create_aaps' => true,
	'edit_aaps' => true,
	'edit_others_aaps' => true,
	'edit_private_aaps' => true,
	'edit_published_aaps' => true,
	'read_private_aaps' => true,
	'edit_dashboard' => true,
	'read' => true,
	'upload_files' => true

Here’s the code to create a Policy Approver of Academic Affairs.

/* Policy Approvers can do everything Policy Editors can do - plus actually publish their respective CPT. */
add_role('policy_approver_aap', 'Approver Academic Affairs', array(
	'create_aaps' => true,
	'edit_aaps' => true,
	'edit_others_aaps' => true,
	'edit_private_aaps' => true,
	'edit_published_aaps' => true,
	'publish_aaps' => true,
	'read_private_aaps' => true,
	'edit_dashboard' => true,
	'read' => true,
	'upload_files' => true

Now this code doesn’t create actual users or assign these roles to anyone – it just tells WordPress, “if a user has the Editor Academic Affairs role, here is what they can do.” So the last step is to create or edit users on your site – just go to their user profile and choose the new role from the dropdown “role” list. If you’re working on a test site, which I hope you are, one nice trick to add a bunch of users without creating a bunch of email addresses is to use your Gmail address for everyone. You can actually add text to your gmail address and it will still get to you! Here’s what I mean:

Say you have the address “”. (Lucky you!) You can add a + symbol right after “bob,” then any text you like, before the @ symbol. So you could have

This satisfies WordPress’ requirement that every user must have a unique email address – but every email you receive will actually go to your Gmail account. This makes it easier to test out all of the fancy workflow notifications you’ll be receiving, and in case you’re wondering, Gmail will show that “to” field when you view your email so you’ll know that “Bob the Academic Affairs Policy Editor” received a particular email and not “Bob the Super Admin of Everything.”

This post is part of a series. Next up: Workflow 1.0: creating the workflow

Leave a Reply

Your email address will not be published. Required fields are marked *