The plugin registers two custom post types: Artist and Album . Both implement comprehensive capability management, custom labels, and REST API integration.
Artist Post Type
The Artist post type represents musicians or bands.
Registration
Location: inc/Content/Artist.php:41
private function register () : void
{
register_post_type ( Definitions :: POST_TYPE_ARTIST , [
'description' => '' ,
'public' => true ,
'publicly_queryable' => true ,
'show_in_nav_menus' => true ,
'show_in_admin_bar' => true ,
'exclude_from_search' => false ,
'show_in_rest' => true ,
'show_ui' => true ,
'show_in_menu' => true ,
'menu_position' => 21 ,
'menu_icon' => 'dashicons-id' ,
'can_export' => true ,
'delete_with_user' => false ,
'hierarchical' => false ,
'has_archive' => 'artists' ,
'query_var' => Definitions :: POST_TYPE_ARTIST ,
'capability_type' => Definitions :: POST_TYPE_ARTIST ,
'map_meta_cap' => true ,
// ... capabilities and labels
]);
}
Definitions::POST_TYPE_ARTIST is a constant defined as 'music_artist' in inc/Support/Definitions.php:21
Custom Capabilities
Location: inc/Content/Artist.php:64
The Artist post type uses a custom capability type to provide granular permission control:
'capabilities' => [
// meta caps (don't assign these to roles)
'edit_post' => 'edit_music_artist' ,
'read_post' => 'read_music_artist' ,
'delete_post' => 'delete_music_artist' ,
// primitive/meta caps
'create_posts' => 'create_music_artists' ,
// primitive caps used outside of map_meta_cap()
'edit_posts' => 'edit_music_artists' ,
'edit_others_posts' => 'edit_others_music_artists' ,
'publish_posts' => 'publish_music_artists' ,
'read_private_posts' => 'read_private_music_artists' ,
// primitive caps used inside of map_meta_cap()
'read' => 'read' ,
'delete_posts' => 'delete_music_artists' ,
'delete_private_posts' => 'delete_private_music_artists' ,
'delete_published_posts' => 'delete_published_music_artists' ,
'delete_others_posts' => 'delete_others_music_artists' ,
'edit_private_posts' => 'edit_private_music_artists' ,
'edit_published_posts' => 'edit_published_music_artists'
],
These capabilities are granted to administrators during plugin activation (see inc/Lifecycle.php:67).
URL Structure
Location: inc/Content/Artist.php:118
'rewrite' => [
'slug' => 'artists' ,
'with_front' => false ,
'pages' => true ,
'feeds' => true ,
'ep_mask' => EP_PERMALINK ,
],
This creates URLs like:
Single artist: https://example.com/artists/the-beatles/
Archive: https://example.com/artists/
Feed: https://example.com/artists/feed/
Supported Features
Location: inc/Content/Artist.php:127
'supports' => [
'title' ,
'editor' ,
'excerpt' ,
'author' ,
'custom-fields' ,
'thumbnail'
]
Custom Admin Messages
The Artist class customizes WordPress admin messages for better UX.
Title Placeholder
Location: inc/Content/Artist.php:141
private function enterTitleHere ( string $title , WP_Post $post ) : string
{
return Definitions :: POST_TYPE_ARTIST === $post -> post_type
? esc_html__ ( 'Enter artist title' , 'bifrost-music' )
: $title ;
}
Post Update Messages
Location: inc/Content/Artist.php:167
private function postUpdatedMessages ( array $messages ) : array
{
global $post , $post_ID ;
$artist_type = Definitions :: POST_TYPE_ARTIST ;
if ( $artist_type !== $post -> post_type ) {
return $messages ;
}
// Get permalink and preview URLs.
$permalink = get_permalink ( $post_ID );
$preview_url = get_preview_post_link ( $post );
// Post updated messages.
$messages [ $artist_type ] = array (
1 => esc_html__ ( 'Artist updated.' , 'bifrost-music' ) . $view_link ,
6 => esc_html__ ( 'Artist published.' , 'bifrost-music' ) . $view_link ,
// ... more messages
);
return $messages ;
}
Album Post Type
The Album post type represents music albums and can be associated with an Artist parent.
Registration
Location: inc/Content/Album.php:49
The Album registration is similar to Artist but includes additional features:
private function register () : void
{
register_post_type ( Definitions :: POST_TYPE_ALBUM , [
'description' => '' ,
'public' => true ,
'show_in_rest' => true ,
'show_ui' => true ,
'menu_position' => 22 ,
'menu_icon' => 'dashicons-playlist-audio' ,
'has_archive' => 'albums' ,
'hierarchical' => false ,
// ... rest of configuration
]);
}
hierarchical is set to false because we’re using the parent-child relationship without the hierarchical UI.
REST API Parent Field
Since the Album post type is non-hierarchical, the parent field isn’t exposed in REST by default. We register it manually:
Location: inc/Content/Album.php:150
private function restRegister () : void
{
register_rest_field ( Definitions :: POST_TYPE_ALBUM , 'parent' , [
'get_callback' => fn ( $post ) => ( int ) $post [ 'parent' ],
'update_callback' => fn ( $value , $post ) => wp_update_post ([
'ID' => $post -> ID ,
'post_parent' => ( int ) $value
]),
'schema' => [
'description' => __ ( 'Parent Artist ID' , 'bifrost-music' ),
'type' => 'integer' ,
'context' => [ 'view' , 'edit' ]
]
]);
}
This allows the block editor to:
Read the current parent artist ID
Update the parent artist via REST API
Admin List Columns
The Album post type adds a custom “Artist” column to the admin list table.
Adding the Column
Location: inc/Content/Album.php:237
private function addParentColumn ( array $columns ) : array
{
$new_columns = [];
foreach ( $columns as $key => $value ) {
$new_columns [ $key ] = $value ;
// Add parent column after title
if ( 'title' === $key ) {
$new_columns [ 'parent_artist' ] = __ ( 'Artist' , 'bifrost-music' );
}
}
return $new_columns ;
}
Displaying Column Content
Location: inc/Content/Album.php:256
private function displayParentColumn ( string $column , int $post_id ) : void
{
if ( 'parent_artist' === $column ) {
$parent_id = wp_get_post_parent_id ( $post_id );
if ( $parent_id && $parent = get_post ( $parent_id )) {
if ( current_user_can ( 'edit_post' , $parent_id )) {
printf (
'<a href="%s">%s</a>' ,
esc_url ( get_edit_post_link ( $parent_id )),
esc_html ( $parent -> post_title )
);
return ;
}
echo esc_html ( $parent -> post_title );
return ;
}
echo '–' ;
}
}
Making it Sortable
Location: inc/Content/Album.php:283
private function sortParentColumn ( array $columns ) : array
{
$columns [ 'parent_artist' ] = 'parent' ;
return $columns ;
}
Capability Management
Capabilities are added during plugin activation and removed during uninstall.
Activation
Location: inc/Lifecycle.php:45
public static function activate () : void
{
if ( $role = get_role ( 'administrator' )) {
// Artist caps.
$role -> add_cap ( 'create_music_artists' );
$role -> add_cap ( 'edit_music_artists' );
$role -> add_cap ( 'edit_others_music_artists' );
$role -> add_cap ( 'publish_music_artists' );
// ... more capabilities
// Album caps.
$role -> add_cap ( 'create_music_albums' );
$role -> add_cap ( 'edit_music_albums' );
// ... more capabilities
}
}
Uninstall
Location: inc/Lifecycle.php:93
public static function uninstall () : void
{
if ( $role = get_role ( 'administrator' )) {
// Remove Artist caps
$role -> remove_cap ( 'create_music_artists' );
$role -> remove_cap ( 'edit_music_artists' );
// ... remove all capabilities
}
}
Capabilities are not removed on deactivation, only on uninstall. This prevents data loss if the plugin is temporarily deactivated.
Service Registration
Both post types are registered as singletons in the service container:
Location: inc/Content/ContentServiceProvider.php:24
public function register () : void
{
$this -> container -> singleton ( Album :: class );
$this -> container -> singleton ( Artist :: class );
$this -> container -> singleton ( Genre :: class );
}
public function boot () : void
{
$this -> container -> get ( Album :: class ) -> boot ();
$this -> container -> get ( Artist :: class ) -> boot ();
$this -> container -> get ( Genre :: class ) -> boot ();
}
Key Takeaways
Custom Capabilities Use capability_type and custom capabilities for fine-grained permission control
REST API Integration Register custom fields with register_rest_field() for non-hierarchical parent relationships
Admin UX Enhance the admin experience with custom messages, columns, and title placeholders
Clean Uninstall Remove all capabilities during uninstall to leave no traces
Next Steps
Taxonomies Learn how the Genre taxonomy is implemented
Block Editor Integration See how the parent artist selector works in the block editor