Epic WP Guide to Custom Post Type Permissions with Map Meta Caps

Setting up custom capabilities (permissions settings) for custom post types. It’s a topic that has frustrated many developers from novice all the way to expert. It’s the type of technical topic that tends to require more than a surface level understanding. Following a tutorial often isn’t enough to grasp it in a way that allows you to fully unleash the power and flexibility of the WP capabilities system. The good news is this Epic WP Guide from GoldHat Group is going to give you an in-depth exploration of how to use custom capabilities with CPT’s, how to use the map_meta_caps filter, and what to expect from the WP admin when you take various approaches to capability mapping.

Because this probably isn’t your first stop in making sense of how to use capabilities for CPT’s let’s start with an overview of the process before we begin to delve into the details. This epic guide is only going to involve 3 steps. That’s the good news. The bad news is there is enough complexity that we’ll be looping over these 3 steps a total of 3 times each in order to help your brain assimilate the information.

  1. How to use the capabilities settings (“capability_type”, “capabilities”, “map_meta_cap”) when registering CPT’s.
  2. Using the map_meta_cap filter to map primitive capabilities to meta capabilities.
  3. How to assign capabilities to roles and/or users.

CPT Capabilities Settings

When you setup your CPT there are 3 settings we care about that will affect how capabilities are mapped. These are “capability_type”, “capabilities”, “map_meta_cap”.

A common mistake is using capability_type and capabilities. This appears to cause conflicts, however we don’t have an authorized statement from any official docs saying don’t use them together. It’s just been our experience that using either setting alone works as intended, whereas using “capability_type” AND “capabilities” appears to cause either a problem with mapping capabilities which we handle later with the map_meta_cap OR it simple interferes with how permissions are applied. Whatever the case we advise using one or the other, not both. The “map_meta_cap” defaults to false, it should be set to true only when using “capabilities”.

To summarize you have 2 major configuration options using the 3 capabilities settings:

  1. Use capability_type (which defaults to string “post”). We call this a “semi-custom” approach, because WP will automatically create custom capabilities and map them for you. You won’t control the naming, that’s done by convention, and you won’t have control over the exact mapping that part will be done in a standard way. However before disregarding this approach, consider that you will still have extensive options to impose logic and affect permission utilizing the map_meta_cap filter. Ask yourself do I need more, and if so why? The more you understand how custom mapping of capabilities works, the more appropriately you’ll be able to make this decision. With this approach you DO NOT pass the “capabilities” or “map_meta_cap”. If you have a situation where these are being automatically set – you would need to pass an empty array for capabilities and false for map_meta_cap. These are both the defaults.
  2. Use custom capability mapping. This is the “fully-custom” approach, gives the maximum control. It allows you to pass an array of your custom capabilities, and to map them to the post capabilities which we’ll discuss in detail later. Using this approach allows you to control naming and to map multiple default capabilities to a single custom capability that you define. When using this approach DO NOT pass “capability_type”. You must set the “map_meta_cap” to true for this to work, and you’ll need to use the map_meta_cap filter.

Using Capabilities Settings in CPT’s

This is not a beginners guide to WP permissions so if you’re entirely new to capabilities and never used functions like user_can() we recommend brushing up on those basics. What we will cover here is how capabilities are mapped in relation to custom post types.

There are 2 types of capabilities: meta and primitive. Imagine meta capabilities as being major highways, routes or freeways. Let’s say you’re in a big city and you have 3 of these major highways, the one is called highway 5 another route 9, and finally we have route 17. If you want to head east, everybody knows, take the 9. You could be going to vastly different places, but you start with this major roadway. Same idea with capabilities. Always start by thinking about the meta capabilities, which will lead you to the primitives. You’ll see a lot of texts explaining these things in reverse, talking in terms of mapping primitives to meta capabilities. And it’s true, as you’ll see later when we work with the map_meta_caps filter that yes we do end up spending more time focused on the primitives. But that’s just like when driving someplace, you spend most of your time navigating the exits and smaller turns to find a place. You’ll never get even close if you don’t the right highway!

There are only 3 meta capabilities. Memorize them: edit, delete, read. For posts the actual name of them always ends with “post” so we have the list shown below:

  • edit_post
  • delete_post
  • read_post

Meta capabilities are not mapped to roles, whereas primitive capabilities are. The relationships here between the role, the primitive capability and the meta capability can be understood as follows:

  • Imagine you have a jug of water and a cup. As owner of the jug and the cup you can do whatever you want with either one, including pour the water in the cup, pick up the cup, drink from the cup and finally smash the cup on the ground.
  • Your child, can also pick up the cup and drink but cannot fill the cup or smash the cup!
  • Your dog isn’t allowed anywhere near the cup! He has full permissions only for the dish.
  • Our roles are owner/child/dog. The PRIMITIVE capabilities are pickup_cup, drink, fill_cup, smash_cup. The META capabilities are use_cup, destroy_cup.
  • When any of our users in this scenario try to pickup the cup, the question is asked “can they use the cup”? The question at this point is in the context of the meta capability, use_cup. We don’t directly get the answer, instead use_cup will reference which primitive capability(s) are required. The logic involved will take into account the user specific context, such as whether the user is the owner of the cup, or whether the cup has already been smashed!

To see the full list of capabilities available to map for a custom post type it’s worth reading the section at https://codex.wordpress.org/Function_Reference/register_post_type under the heading capability_type and capabilities. As mentioned earlier using the capability_type setting enables a “semi-custom” approach to mapping capabilities. As shown in the docs example, if the custom post type was “book”, setting capability_type to “book” would result in the capabilities shown below:

Now let’s look at an example of the fully custom capability mapping that is more commonly used when building plugins. When we want full control, and perhaps a different naming convention. This is form our QuizMaster plugin, notice that we prefix all our capabilities with “quizmaster_” which keeps them consistent with the naming conventions used in other areas of the plugin.

You’ll notice our custom definition of capabilities includes primitive capabilities such as “edit_published_posts” that are not automatically mapped when using the capability_type automatic mapping. This is because WP core generally does not use these in the WP admin. These other capabilities however can be very useful in providing more fine-grained control later when we define permissions logic in the map_meta_caps filter.

Mapping Capabilities with the Map_Meta_Caps Filter

The most common place where developers go wrong is in failing to utilize the map_meta_caps filter when they’ve set map_meta_cap to true in their CPT definition. If you’ve set map_meta_cap to true, you must use the map_meta_caps filter because generally speaking, all access will be denied until you return capabilities from this filter. You might think not using the filter would open permissions up, but quite the opposite in what may be a safeguard, no filter return means no permissions granted. That’s not to suggest that the purpose of map_meta_caps filter is to determine if permission is granted. It’s job is much more limited. It merely answers the question given the current context including the meta capability, the post, the user, what primitive capability would the user need to do what their trying to do?