How to add custom columns in the posts list with PHP in WordPress

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

  1. 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.
  2. Empty values: Confirm meta keys and taxonomy slugs are correct. Use get_post_meta(post_id, your_key, true) and verify values exist.
  3. 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.
  4. 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 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *