It has to do with the way the admin post table is constructed. WordPress uses a class WP_Posts_List_Table to generate and handle that table. You’ll notice that there are a few tables in the admin area similar: Plugins, Terms, Users, etc. They each are their own class, but they all extend from a base class of WP_List_Table.
manage_edit-{$post_type}_columns is a more generic filter that is set (but not called) in WP_List_Table. And, it is actually manage_{$screen->id}_columns. The callback is the get_columns method for the table object.
add_filter( "manage_{$this->screen->id}_columns", array( &$this, 'get_columns' ), 0 );
This filter is used by the get_column_headers() function for retrieving the columns.
So where does manage_{$post_type}_posts_columns then? Remember the method get_columns() that is call for the filter manage_{$screen->id}_columns, the WP_Posts_List_Table class over-rides the base method from WP_List_Table. In WP_Posts_List_Table::get_columns() is where the filter manage_{$post_type}_posts_columns is called.
To recap:
- A
WP_Posts_List_Tableobject is created - It’s parent class
WP_List_Tableadds$this->get_columns()to the hookmanage_{$screen->id}_columns - The function
get_column_headers()calls the filtermanage_{$screen->id}_columns WP_Posts_List_Tablehas over-ridden the methodget_columns()- Inside of
WP_Posts_List_Table::get_columns(), it calls the filtermanage_{$post_type}_posts_columnsThis is where your manage_{$post_type}_posts_columns hook would run WP_Posts_List_Table::get_columns()will return the columns and then run the remaining filters formanage_{$screen->id}_columns
Which one should you use? I don’t know. We know that manage_{$post_type}_posts_columns can only be called for post types, whereas manage_edit-{$post_type}_columns may match a different admin page (although I’m not sure if it would).