I'm answering myself because today I asked myself the 2 questions I posted, and spent some time to find an answer. Once I found a solution, I want to share it, but any other solution is higly apprecciated and I'm ready to accept any solution that I found better than mine.
Edits and improvements to my solution are appreciated as well nay encouraged.
Edit
After Rarst answer I've edited the code. Now function use standard dashicons classes, but also allow to specify an old style image url in menu_icon
argument and a brand new dashicons class in the menu_dashicon
argument.
Workflow
First think I thought was that register_post_type
, fires an action, registered_post_type
, that pass to hooking functions the arguments passed to register_post_type
, without filtering them, so is possible to creat custom arguments to that functions.
So I decided to pass the argument 'menu_dashicon'
to pass a custom dashicon.
After that I thought to create a class that listen to that argument, saving icon in a class variable. The same class can be responsible to
- check the currnt version of WP, and if it is less then 3.8 do nothing
- if version is 3.8+ loop the
$menu
array on proper hook and:
- remove, if present, any custom images added via
'menu_icon'
and
- add the inline style according the what is added via the
'menu_dashicon'
param
I create the code in a single file, in this way it can be easily included in any theme/plugin or even used as MU plugin and after that one can just use the brand new 'menu_dashicon'
argument in every theme and/or plugin installed.
I've also added a minimal plugin header that allow to use it as a standalone plugin, but probably that's the less usefull way to use.
How to use
Inside register_post_type
just pass the 'menu_dashicon'
argument with the value of the dashicon class (without prefix 'dashicons-'):
$args = array(
...
'menu_dashicon' => 'chart-pie', // dashicons will be used in WP 3.8+
'menu_icon' => $url_of_the_icon // icon images will be used in WP 3.7.1 & previous
);
register_post_type('my_cpt', $args);
That's all. Get Dashicons icon class name from its site.
So here the code:
<?php
/**
* Plugin Name: GM CPT Icon
*/
namespace GM;
class CptIcon {
public static $cpt;
public $css;
static function registerIcon( $cpt, $icon ) {
self::$cpt[$cpt] = $icon;
}
function init() {
if ( $this->mp6() ) {
\add_action('admin_menu', array($this, 'parseMenu') );
}
}
function mp6() {
return \version_compare( $GLOBALS['wp_version'], '3.8', '>=' );
}
function parseMenu() {
if ( $this->mp6() && ! empty( self::$cpt ) ) {
foreach ( $GLOBALS['menu'] as $i => $item ) {
if $item[1] === 'edit_posts' && (strpos($item[2], 'edit.php?post_type=') === 0)) {
$this->menuItemClass($i, str_replace('edit.php?post_type=', '', $item[2]));
}
}
}
}
function menuItemClass( $i, $type ) {
if ( \in_array($type, \array_keys(self::$cpt), TRUE ) ) {
$GLOBALS['menu'][$i][4] = str_replace('menu-icon-post', '', $GLOBALS['menu'][$i][4]);
$GLOBALS['menu'][$i][6] = 'dashicons-' . self::$cpt[$type];
}
}
}
\add_action('plugins_loaded', function() {
if ( \is_admin() && !( \defined('DOING_AJAX') && \DOING_AJAX ) ) {
$cpticon = new CptIcon;
$cpticon->init();
}
});
\add_action('registered_post_type', function( $post_type, $args ) {
if ( isset($args->menu_dashicon) && ! empty($args->menu_dashicon) ) {
CptIcon::registerIcon($post_type, $args->menu_dashicon);
}
}, 10, 2);
It is also available as Gist

Two CPTs: "Ideas" and "Gallery" using Dashicons. Note auto color change with different admin color schemes.