Contents
Introduction
This article is a comprehensive, step-by-step tutorial that shows how to add and manage custom columns in the WordPress Posts list using PHP. You will learn the hooks involved, how to display different kinds of data (meta, featured image, taxonomies), how to make columns sortable, how to style them in the admin, performance and security considerations, and a couple of full working examples you can drop into a plugin or functions.php.
Requirements and context
- WordPress version: The API used here is supported in recent WordPress versions these examples use standard admin hooks that are widely available.
- Where to add the code: Put code in a small plugin or inside your themes functions.php for development. A plugin is recommended for portability.
- Post type: Examples target the built-in post type post. For other post types replace the hooks accordingly (explained below).
Quick overview of the hooks you need
- Filter to register columns: manage_edit-{post_type}_columns (e.g. manage_edit-post_columns)
- Action to populate column cells: manage_{post_type}_posts_custom_column (e.g. manage_post_posts_custom_column)
- Filter to register sortable columns: manage_edit-{post_type}_sortable_columns
- Modify query when sorting: pre_get_posts
- Admin CSS for column width/appearance: admin_head or enqueue admin-style
Basic example — add a simple Reading Time column
This example shows the minimal two steps: register the column, then print the value. The reading time is calculated quickly and output as plain text. If you want sorting, store reading time as post meta and use the sortable example later.
lt?php // 1) Register the column add_filter( manage_edit-post_columns, myplugin_add_reading_time_column ) function myplugin_add_reading_time_column( columns ) { // Insert the new column after title (or modify order as you like) new_columns = array() foreach ( columns as key =gt value ) { new_columns[ key ] = value if ( title === key ) { new_columns[reading_time] = __( Reading Time, my-textdomain ) } } return new_columns } // 2) Output the column content add_action( manage_post_posts_custom_column, myplugin_show_reading_time_column, 10, 2 ) function myplugin_show_reading_time_column( column, post_id ) { if ( reading_time !== column ) { return } // Very simple reading time calculation content = get_post_field( post_content, post_id ) word_count = str_word_count( wp_strip_all_tags( content ) ) minutes = max( 1, (int) ceil( word_count / 200 ) ) // 200 wpm average echo esc_html( minutes . min ) } ?gt
Notes
- Use manage_edit-post_columns to change columns for the Post list screen.
- Use manage_post_posts_custom_column to output content for post type post. The pattern is manage_{post_type}_posts_custom_column.
- If you add heavy computations, cache results or store them as post meta to avoid slowing down the admin list (see performance).
Featured image (thumbnail) column
Many editors like to see the featured image directly in the posts list. This example outputs a small thumbnail and handles missing thumbnails gracefully.
lt?php // Add thumbnail column add_filter( manage_edit-post_columns, myplugin_add_thumbnail_column ) function myplugin_add_thumbnail_column( columns ) { columns = array_slice( columns, 0, 1, true ) // before first column array( thumbnail =gt ) // our thumbnail column array_slice( columns, 1, null, true ) // the rest return columns } // Render thumbnail add_action( manage_post_posts_custom_column, myplugin_show_thumbnail_column, 10, 2 ) function myplugin_show_thumbnail_column( column, post_id ) { if ( thumbnail !== column ) { return } size = array( 80, 80 ) // width, height if ( has_post_thumbnail( post_id ) ) { echo get_the_post_thumbnail( post_id, size ) } else { // placeholder: small transparent or icon echo ltspan style=display:inline-blockwidth:80pxheight:80pxbackground:#f3f3f3text-align:centerline-height:80pxcolor:#999gt—lt/spangt } } ?gt
Accessibility and performance
- Use proper alt text on thumbnails where appropriate get_the_post_thumbnail outputs the image with alt attribute from the attachment.
- Limit image size to avoid large HTML response use a small thumbnail size.
Display custom field (post meta) and make it sortable
When data lives in post meta and you want to sort by it in the admin list, do two things: register the column and sortable label, then modify the main query on pre_get_posts to adjust orderby and meta_key.
lt?php // Example: display a numeric meta value priority and make it sortable // Register column add_filter( manage_edit-post_columns, myplugin_add_priority_column ) function myplugin_add_priority_column( columns ) { columns[priority] = __( Priority, my-textdomain ) return columns } // Render cell add_action( manage_post_posts_custom_column, myplugin_show_priority_column, 10, 2 ) function myplugin_show_priority_column( column, post_id ) { if ( priority !== column ) { return } value = get_post_meta( post_id, priority, true ) if ( === value ) { echo ndash // dash for empty } else { echo esc_html( value ) } } // Make column sortable add_filter( manage_edit-post_sortable_columns, myplugin_make_priority_sortable ) function myplugin_make_priority_sortable( columns ) { columns[priority] = priority return columns } // Adjust query for sorting add_action( pre_get_posts, myplugin_priority_orderby ) function myplugin_priority_orderby( query ) { if ( ! is_admin() ) { return } orderby = query->get( orderby ) if ( priority === orderby ) { // Tell WP to sort by numeric meta value query->set( meta_key, priority ) query->set( orderby, meta_value_num ) } } ?gt
Important notes on sorting by meta
- Sorting by meta can be slow on sites with many posts. If possible, use a dedicated table or store sortable fields in columns in a custom table for large datasets.
- Use meta_value_num for numeric meta to get numeric ordering meta_value for string ordering.
- Ensure meta exists for all relevant posts or handle missing values (they may sort to top or bottom depending on DB).
Taxonomy terms column
Display associated taxonomy terms (categories, tags, or custom taxonomies) in their own column.
lt?php // Example: show custom taxonomy genre terms in posts list add_filter( manage_edit-post_columns, myplugin_add_genre_column ) function myplugin_add_genre_column( columns ) { columns[genre] = __( Genre, my-textdomain ) return columns } add_action( manage_post_posts_custom_column, myplugin_show_genre_column, 10, 2 ) function myplugin_show_genre_column( column, post_id ) { if ( genre !== column ) { return } terms = get_the_terms( post_id, genre ) if ( is_wp_error( terms ) empty( terms ) ) { echo ndash return } out = array() foreach ( terms as term ) { out[] = esc_html( term->name ) } echo implode( , , out ) } ?gt
Styling columns (width, alignment) in the admin
Use admin_head to output some CSS that targets the column by class. WordPress adds a column-specific class of column-{name} to the TDs and THs.
/ admin CSS: restrict width of thumbnail and center contents / .wp-admin .column-thumbnail { width: 100px text-align: center } .wp-admin .column-reading_time, .wp-admin .column-priority { width: 90px text-align: center }
lt?php // Print the CSS into admin head for posts screen add_action( admin_head-edit.php, myplugin_admin_post_list_css ) function myplugin_admin_post_list_css() { echo ltstylegt echo .wp-admin .column-thumbnail { width:100px text-align:center } echo .wp-admin .column-reading_time, .wp-admin .column-priority { width:90px text-align:center } echo lt/stylegt } ?gt
Reordering or removing default columns
You can reorder or remove default columns by modifying the array your filter returns. The columns array keys matter and determine order. Example: remove the comments column:
lt?php add_filter( manage_edit-post_columns, myplugin_modify_columns ) function myplugin_modify_columns( columns ) { // Remove comments column unset( columns[comments] ) // Reorder: put date first date = columns[date] unset( columns[date] ) new = array( date =gt date ) columns return new } ?gt
Working with custom post types
For custom post types replace post in the hooks. Use the dynamic patterns:
- Filter: manage_edit-{post_type}_columns
- Action: manage_{post_type}_posts_custom_column
- Sortable: manage_edit-{post_type}_sortable_columns
Example for a custom post type book: add_filter(manage_edit-book_columns, …), add_action(manage_book_posts_custom_column, …).
Performance considerations
- Avoid heavy queries per row: For every post row the action runs avoid running separate expensive queries. Use get_post_meta (it is cached with object cache) and WP functions that use cached data whenever possible.
- Batch fetch if necessary: If you must retrieve data for many posts (for example, custom table lookups), consider retrieving data for the visible posts in a single query by using the global post_ids list on admin screens or hooking earlier to fetch and cache results.
- Store precomputed sortable values: If you want fast sorting by calculation (reading time, score), store the result in post meta whenever the post is saved and use that meta for sorting.
Security and escaping
- Escape all output with esc_html(), esc_url(), wp_kses_post(), etc., depending on content type.
- Do not trust any meta or taxonomy values sanitize on output and when saving.
- Be cautious if adding HTML inside cells avoid inline scripts.
Advanced: use WP_List_Table for full control
If you need more advanced features (bulk actions, custom pagination, complex column rendering) consider extending WP_List_Table in a custom admin page. For most needs, manage_edit-… hooks are simpler and integrate with WordPress list screen controls (quick edit, bulk actions, filters).
Full combined example
The following combined example adds four columns to the Posts list: thumbnail, reading time (meta-cached), priority (sortable meta), and genre (taxonomy). It also adds admin CSS. This is a ready-to-use snippet for functions.php or a small plugin. The reading time is cached as a post meta on save for performance so it is sortable if desired.
lt?php // 0) Hook to save cached reading time when post is saved add_action( save_post_post, myplugin_cache_reading_time, 10, 3 ) function myplugin_cache_reading_time( post_id, post, update ) { // Only for posts and not auto-saves/revisions if ( wp_is_post_revision( post_id ) wp_is_post_autosave( post_id ) ) { return } content = post->post_content word_count = str_word_count( wp_strip_all_tags( content ) ) minutes = max( 1, (int) ceil( word_count / 200 ) ) update_post_meta( post_id, _reading_time, minutes ) } // 1) Register our columns add_filter( manage_edit-post_columns, myplugin_add_many_columns ) function myplugin_add_many_columns( columns ) { // Remove the date column and then append our columns before it if ( isset( columns[date] ) ) { date = columns[date] unset( columns[date] ) } else { date = } // Insert our columns columns[thumbnail] = columns[reading_time] = __( Reading Time, my-textdomain ) columns[priority] = __( Priority, my-textdomain ) columns[genre] = __( Genre, my-textdomain ) // Add back date at the end if ( date ) { columns[date] = date } return columns } // 2) Output column contents add_action( manage_post_posts_custom_column, myplugin_render_many_columns, 10, 2 ) function myplugin_render_many_columns( column, post_id ) { switch ( column ) { case thumbnail: if ( has_post_thumbnail( post_id ) ) { echo get_the_post_thumbnail( post_id, array(80,80) ) } else { echo ndash } break case reading_time: rt = get_post_meta( post_id, _reading_time, true ) if ( === rt ) { // fallback: calculate quickly content = get_post_field( post_content, post_id ) words = str_word_count( wp_strip_all_tags( content ) ) rt = max( 1, (int) ceil( words / 200 ) ) } echo esc_html( rt . min ) break case priority: priority = get_post_meta( post_id, priority, true ) if ( === priority ) { echo ndash } else { echo esc_html( priority ) } break case genre: terms = get_the_terms( post_id, genre ) if ( is_wp_error( terms ) empty( terms ) ) { echo ndash } else { names = wp_list_pluck( terms, name ) echo esc_html( implode( , , names ) ) } break } } // 3) Make priority and reading_time sortable add_filter( manage_edit-post_sortable_columns, myplugin_sortable_columns ) function myplugin_sortable_columns( columns ) { columns[priority] = priority columns[reading_time] = reading_time return columns } // 4) Adjust the query for sorting add_action( pre_get_posts, myplugin_sortable_columns_orderby ) function myplugin_sortable_columns_orderby( query ) { if ( ! is_admin() ) { return } orderby = query->get( orderby ) if ( priority === orderby ) { query->set( meta_key, priority ) query->set( orderby, meta_value_num ) } elseif ( reading_time === orderby ) { query->set( meta_key, _reading_time ) query->set( orderby, meta_value_num ) } } // 5) Admin CSS for column widths add_action( admin_head-edit.php, myplugin_admin_css ) function myplugin_admin_css() { echo ltstylegt echo .wp-admin .column-thumbnail{width:100pxtext-align:center} echo .wp-admin .column-reading_time, .wp-admin .column-priority{width:85pxtext-align:center} echo .wp-admin .column-genre{width:200px} echo lt/stylegt } ?gt
Troubleshooting — common issues and fixes
- Column not appearing: Verify your filter hook is correct (manage_edit-{post_type}_columns) and that your code runs in admin. Disable object caching issues and ensure no other plugin removes your columns.
- Empty values: Confirm meta keys and taxonomy slugs are correct. Use get_post_meta(post_id, your_key, true) and verify values exist.
- Sorting not working: Ensure you added your column to the sortable columns filter and changed the query in pre_get_posts by checking the orderby GET variable. For meta sorting set the correct meta_key and use meta_value_num for numeric values.
- Performance slow: Avoid heavy per-row queries cache or prefetch data for all visible post IDs.
Best practices and tips
- Prefer plugins for customizations used across themes.
- Cache computed values as post meta if you need to sort or display them frequently.
- Sanitize inputs and escape output.
- Test on staging before deploying to a live site, especially for queries that modify the main posts query.
- For very complex admin views consider building a custom WP_List_Table to isolate complexity from the standard posts screen.
Useful references
- WordPress developer reference: use the function names mentioned above (manage_edit-…, manage_…_posts_custom_column, pre_get_posts).
- If you want official docs, consult the core WordPress developer resources (search for the filter/action names shown above).
Conclusion
Adding custom columns to the WordPress posts list is a powerful way to present important data to editors and administrators. Using the manage_edit-{post_type}_columns and manage_{post_type}_posts_custom_column hooks you can add nearly any content to the list, and with the sortable-columns filter plus pre_get_posts you can create admin-sortable fields. Follow performance and security best practices: cache heavy computations, sanitize and escape output, and scale with care.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |