When you create a custom post type in WordPress, you can set it to ‘support’ a number of things: a featured image, trackbacks, revisions… and author. You might want to use it in the way author is used for core posts, literally the credited author of a post. Or maybe you could use it to give a bit more granularity to user permissions. The ‘owner’ of a post, if you will. Great!
Well, that’s what I wanted to do. Turns out it’s a little more complicated…
I set up my custom post types with their own capabilities, and assigned them to the right roles using Justin Tadlock’s trusty Members plugin. (This process might form another post if I get time. This post of Justin’s definitely helps. My code based on his code might help if you’re stuck. The sticking point for me was the fact that you have to manually add your CPT capabilities to at least one role in the Members role manager, using the little input at the bottom of the role edit screen. NOTE: In the new version 1.x.x of the plugin, the input is at the side, and it seems so far that you have to add them while the Custom tab is selected down the left – even if you can already see the caps in the CPT’s own tab. Anyway…)
The idea is that since you’ve got something like
edit_others_posts for your specific CPT, a user in a role without that capability can be set by a user in a role with it as the ‘author’. Then the former user, as long as they’ve got the more general
edit_posts equivalent for that CPT, will be able to edit that post, but not others. Great!
Except, when the core authors drop-down is output, you probably won’t see any users who are in custom roles. That’s a big problem if you’re making much use of Members. Why is this?
Turns out it’s a kind of simple, kind of knotty legacy issue related to user levels. These are a deprecated system from WordPress’s early days. The core authors drop-down is populated using
who => 'authors' in a call to
get_users(). And what does that do? It matches against the user level stored in the user meta table. And it seems that non-core roles, because they don’t have a mapping to the primitive user levels, are set to user level zero. Doesn’t matter how many impressive capabilities they have, to
who => 'authors', they’re as puny as subscribers.
This is a 4-year-old ‘known issue’, summed up by the most recent comment on its Trac ticket as ‘a mess’.
I originally thought of solving it by making sure that when a user is assigned to a custom role, they get a user level higher than zero. Sorted, right? Not quite. If you want to use capabilities specific to each custom post type, this is too broad (hence why the whole user levels system was deprecated ages ago). That user level, marking a user as an ‘author’, would apply for all post types.
I imagine there’s probably improvements to be made, but here’s my first stab at a workaround. This approach uses the CMB2 custom fields plugin, though it should be possible to adapt to use other custom field plugins. The basic idea involves:
- Using a custom field instead of the core author drop-down
- Overriding the saving of the field meta, to save it as the
post_author(and vice versa with the meta retrieval)
- A few other tricks
1. Registering support
First off, when you register your custom post type, don’t add
author to the
supports parameter array, add your own version. I use
Obviously, it’ll need to support
2. Registering the custom field
The basic code for this is straight-forward CMB2 code. However, it depends on a few extra functions to do these things:
- To only show the custom field for people who can edit posts of this type authored / owned by someone else
- To get a list of users for the drop-down based on capability rather than role or user level
- To filter the saving and retrieval of the custom field value to use
post_authorin the main
There’s a gist with everything in it:
Here’s what’s happening in the code:
- When registering custom fields, there’s a loop through every custom post type.
- Each CPT is checked to see if it supports our custom author feature.
- The options for the drop-down are built up using a custom function which gets all users by capability. There’s no simple way of doing this – you have to get all users, then check each user for the capability. At least, this function makes sure it only gets all users once per request – but watch out if you’ve a lot of users in your system!
- Each of these CPTs has a meta box registered with the author drop-down.
- I have my own system using
show_on_cbfor custom management of conditional meta box display. In the callback function,
pilau_cmb2_show_on_custom, I’ve left some other stuff unrelated to this task in there – it might be useful / interesting. But the main thing here is the bit which maps
post_typesbit is a placeholder for the CPT on the current screen) to the right capability for that post type.
- The author drop-down defaults to the current user’s ID.
- Right at the end there’s a couple of things hooked to CMB2’s filters for saving and retrieving the meta value for this field. The standard meta procedures are short-circuited and the core
post_authorfield is used instead. (With a bit of fiddling about with the save to avoid an infinite loop…)
Er… that’s all there is to it! ;-)