Customizing WordPress admin list columns can turn the standard Posts, Pages and custom post type screens into much more useful content-management interfaces.
WordPress list tables already display information such as the post title, author, taxonomy terms, comments and publication date. But those defaults are necessarily generic. A real website may need editors to see completely different information while browsing content.
A product catalog might need:
- SKU;
- price;
- supplier;
- stock status.
An editorial website might need:
- content owner;
- workflow status;
- reading time;
- last review date.
A project-management post type might need:
- client;
- deadline;
- priority;
- assigned manager.
WordPress provides dedicated list-table hooks that let developers add, remove, rename, reorder and populate columns without modifying WordPress Core.
The basic architecture is:
WordPress list table
+
column filters
+
column output actions
+
optional sorting
=
customized admin content view
This guide explains how WordPress admin list columns work, how to customize them for Posts, Pages and custom post types, how to add and remove columns, how to control their order, how to populate custom values, how Screen Options and sortable columns fit into the system, and how to avoid performance and usability problems as the table becomes more sophisticated.
How WordPress admin list columns work
When you open a screen such as Posts → All Posts, WordPress renders a list table containing one row for each item and several columns describing that item.
A typical Posts table may contain:
Checkbox
Title
Author
Categories
Tags
Comments
Date
These are not simply hardcoded pieces of HTML that developers must replace manually.
WordPress builds the column configuration as an associative array in which:
array key
→ internal column identifier
array value
→ visible column heading
Conceptually:
$columns = array(
'cb' => '...',
'title' => 'Title',
'author' => 'Author',
'categories' => 'Categories',
'tags' => 'Tags',
'comments' => 'Comments',
'date' => 'Date',
);
The official manage_posts_columns documentation identifies the standard post columns and exposes the array before the table is rendered.
For specific post types, WordPress also provides the dynamic filter:
manage_{$post_type}_posts_columns
The WordPress Developer Reference documents this post-type-specific mechanism.
For example:
manage_post_posts_columns
manage_page_posts_columns
manage_product_posts_columns
manage_project_posts_columns
This makes it possible to customize one content type without changing every list table in the administration area.
For a deeper explanation of the table architecture itself, see WordPress Admin List Tables, Explained.
Column definitions and column contents are separate
WordPress separates two responsibilities:
- which columns exist;
- what each custom column displays.
This distinction is fundamental.
Adding:
$columns['client'] = 'Client';
creates the column heading.
It does not automatically tell WordPress where the Client value comes from.
A separate callback is responsible for rendering the contents of that column for every row.
Add, remove, rename and reorder admin columns
The column array gives developers considerable control over the structure of a WordPress content list.
You do not have to rebuild the entire table. In many cases, modifying the existing array is enough.
Add a new column
Suppose a project custom post type needs a Client column.
function project_admin_columns( $columns ) {
$columns['project_client'] =
__( 'Client', 'project' );
return $columns;
}
add_filter(
'manage_project_posts_columns',
'project_admin_columns'
);
The list table now knows that a column named project_client exists.
Use a specific internal identifier rather than an unnecessarily generic key. WordPress already uses several reserved or built-in column names, and plugins may register their own.
Remove an existing column
If a particular column provides no value for the workflow, remove it from the array:
function project_admin_columns( $columns ) {
unset( $columns['author'] );
unset( $columns['comments'] );
return $columns;
}
This removes those columns from the table definition rather than merely hiding them with CSS.
That difference matters.
CSS such as:
.column-author {
display: none;
}
only changes presentation after WordPress has built the screen.
If a column genuinely should not exist in the interface, modifying the registered column array is a cleaner approach.
Rename an existing column
You can also preserve the underlying column while changing its heading:
function project_admin_columns( $columns ) {
if ( isset( $columns['title'] ) ) {
$columns['title'] =
__( 'Project', 'project' );
}
return $columns;
}
The functionality associated with the Title column remains intact, but the heading better reflects the content model.
Reorder columns deliberately
Simply appending every custom column produces interfaces such as:
Title
Author
Categories
Tags
Comments
Date
Client
Status
Deadline
That may technically work while being poorly organized.
A more useful project table might be:
Title
Client
Status
Deadline
Author
Date
You can rebuild the array in the required order:
function project_admin_columns( $columns ) {
$new_columns = array();
if ( isset( $columns['cb'] ) ) {
$new_columns['cb'] =
$columns['cb'];
}
if ( isset( $columns['title'] ) ) {
$new_columns['title'] =
$columns['title'];
}
$new_columns['project_client'] =
__( 'Client', 'project' );
$new_columns['project_status'] =
__( 'Status', 'project' );
$new_columns['project_deadline'] =
__( 'Deadline', 'project' );
if ( isset( $columns['author'] ) ) {
$new_columns['author'] =
$columns['author'];
}
if ( isset( $columns['date'] ) ) {
$new_columns['date'] =
$columns['date'];
}
return $new_columns;
}
Column order should reflect the decisions users make while scanning the list, not the order in which features happened to be developed.
Populate custom WordPress admin columns
After registering a custom column, you need to provide its value for each row.
For a specific post type, WordPress fires:
manage_{$post_type}_posts_custom_column
The official custom post-type column action documentation confirms that the callback receives the column identifier and current post ID.
For example:
function project_admin_column_content(
$column,
$post_id
) {
if ( 'project_client' !== $column ) {
return;
}
$client = get_post_meta(
$post_id,
'client_name',
true
);
echo '' !== $client
? esc_html( $client )
: '—';
}
add_action(
'manage_project_posts_custom_column',
'project_admin_column_content',
10,
2
);
The complete relationship becomes:
manage_project_posts_columns
→ registers project_client
manage_project_posts_custom_column
→ renders project_client for each project
Different columns can use different data sources
Custom admin columns are not limited to custom fields.
A column can display information derived from:
- post properties;
- post metadata;
- taxonomies;
- featured images;
- related users;
- related posts;
- calculated values;
- plugin data;
- application-specific information.
For example, a Featured Image column could use WordPress’s attachment APIs rather than post metadata.
function project_admin_column_content(
$column,
$post_id
) {
if ( 'project_thumbnail' !== $column ) {
return;
}
if ( ! has_post_thumbnail( $post_id ) ) {
echo '—';
return;
}
echo get_the_post_thumbnail(
$post_id,
array( 60, 60 )
);
}
When outputting values manually, use the correct WordPress escaping function for the context.
The official WordPress escaping documentation recommends escaping output as late as possible.
For plain text:
echo esc_html( $value );
For URLs:
echo esc_url( $url );
For HTML attributes:
echo esc_attr( $value );
Custom fields deserve their own implementation strategy
Post metadata is one of the most common sources for custom columns, but there are additional considerations around missing values, formatting, sorting and metadata performance.
For the complete workflow, see Adding a Custom Field as a Column in WordPress.
Posts, Pages and custom post types are not completely identical
One reason WordPress column tutorials can become confusing is that the exact hooks vary depending on the object being displayed.
Posts
For standard Posts, you can work with:
manage_posts_columns
manage_posts_custom_column
The official manage_posts_custom_column reference notes that this action applies to non-hierarchical post types such as standard posts.
Pages and hierarchical content
Pages are hierarchical.
WordPress provides:
manage_pages_columns
manage_pages_custom_column
The manage_pages_columns filter changes the columns on the Pages list table, while manage_pages_custom_column renders custom values for hierarchical content.
This distinction also matters for hierarchical custom post types.
Custom post types
When the customization belongs to one post type, the dynamic hooks are usually the clearest choice:
manage_book_posts_columns
manage_book_posts_custom_column
manage_event_posts_columns
manage_event_posts_custom_column
manage_project_posts_columns
manage_project_posts_custom_column
Using specific hooks prevents unrelated content types from passing through code they do not need.
It also makes the implementation easier to understand when another developer revisits it later.
For the conceptual differences between WordPress content types, see WordPress Post Types vs. Custom Post Types.
Screen Options, primary columns and responsive behavior
Customizing a list table is not only about what appears on a wide desktop screen.
WordPress also has systems for user-controlled visibility and responsive list-table behavior.
Custom columns can participate in Screen Options
Columns added through WordPress’s normal column filters can appear in the Screen Options interface, allowing users to control whether those columns are visible.
This creates two levels of configuration:
Developer:
Which columns are available?
User:
Which available columns should I see?
That relationship is explored in WordPress Screen Options vs. Custom Columns.
It is important not to confuse:
remove column
≠
hide column for one user
If code removes a column from the registered array, the user cannot simply restore it through Screen Options.
If the column remains registered but the user hides it through Screen Options, the underlying feature still exists.
The primary column has special importance
WordPress list tables identify one column as the primary column.
This matters because responsive table behavior and row actions are associated with the primary column.
WordPress exposes the list_table_primary_column filter for changing it.
For example:
function project_primary_column(
$default,
$screen
) {
if ( 'edit-project' === $screen ) {
return 'title';
}
return $default;
}
add_filter(
'list_table_primary_column',
'project_primary_column',
10,
2
);
Most post-management screens should retain Title as the primary column unless there is a strong interface reason to change it.
Moving or replacing the primary column casually can produce confusing responsive behavior.
Test narrow administration layouts
A table that looks excellent at 1920 pixels can become unusable when:
- the browser window is narrow;
- the WordPress admin menu is expanded;
- several plugins add columns;
- column labels are translated into longer strings;
- users increase browser zoom;
- the interface is accessed from a tablet.
Do not solve every width problem by reducing text until it becomes difficult to read.
Usually the better solution is deciding which information genuinely deserves its own column.
Make useful columns sortable
A custom column does not automatically become sortable simply because it displays a value.
Sorting is a separate list-table capability.
WordPress provides the dynamic filter:
manage_{$screen_id}_sortable_columns
The official WordPress sortable-column documentation describes the mechanism used to declare sortable table headings.
Suppose the Project table contains a Deadline column:
function project_sortable_columns(
$columns
) {
$columns['project_deadline'] =
'project_deadline';
return $columns;
}
add_filter(
'manage_edit-project_sortable_columns',
'project_sortable_columns'
);
This makes WordPress aware that the column can request an ordering operation.
The underlying query must still know how that value should be sorted.
Sorting depends on the underlying data
A native post field, taxonomy value and post-meta value may require different query logic.
For metadata, a common approach is to modify the main administration query through pre_get_posts.
function project_admin_ordering( $query ) {
if (
! is_admin()
|| ! $query->is_main_query()
) {
return;
}
if (
'project' !==
$query->get( 'post_type' )
) {
return;
}
if (
'project_deadline' !==
$query->get( 'orderby' )
) {
return;
}
$query->set(
'meta_key',
'project_deadline'
);
$query->set(
'orderby',
'meta_value'
);
}
add_action(
'pre_get_posts',
'project_admin_ordering'
);
The exact query must match the data type and storage format.
A numeric value may require numeric ordering, while an ISO-style date can often be handled differently from a human-formatted date string.
For the complete architecture, see WordPress List-Table Sorting, Explained.
Do not make every column sortable
Sorting should answer a useful administrative question.
Good candidates include:
- deadline;
- price;
- priority;
- publication year;
- numeric score;
- last review date.
A thumbnail or a long descriptive field generally does not benefit from sorting.
Design columns around workflow, not available data
WordPress can store a considerable amount of information about each item.
That does not mean every value belongs in the list table.
The purpose of an admin column is to reduce the amount of work required to identify, compare or manage records.
Ask what the user needs to know before opening the item
Useful columns answer questions such as:
- Who owns this content?
- What is its current workflow status?
- When is the deadline?
- Which client does it belong to?
- What is the SKU?
- Has the item been approved?
- Which template is it using?
If the information does not help users make a list-level decision, keeping it inside the edit screen may be better.
Avoid the temptation to create a database spreadsheet
Admin list tables are interfaces, not raw database viewers.
A table containing:
Title
ID
Slug
Author
Template
Client
Status
Priority
Deadline
Budget
External ID
Sync ID
SEO Score
Word Count
Reading Time
Modified
Published
Comments
contains plenty of information but may be significantly harder to use.
Columns compete for horizontal space.
More columns can lead to:
- narrow titles;
- multi-line values;
- poor scanning;
- responsive problems;
- visual noise;
- confusion for non-technical users.
For client-facing administration design, see Reducing WordPress Admin Confusion for Clients.
For organizations where multiple people need predictable interfaces, see Standardizing the WordPress Admin for Teams.
Think about role-specific requirements
An administrator, editor and shop manager may need different information.
Before forcing a large universal column configuration, determine whether Screen Options or role-specific administration customization provides a better balance.
The objective is not maximum information density. It is maximum usefulness for the task being performed.
Performance and compatibility considerations
Every custom value needs to be generated for every visible row.
This makes admin columns an easy place to introduce performance problems without realizing it.
Imagine a screen showing:
100 rows
×
4 custom columns
=
400 opportunities to run additional logic
If those callbacks perform lightweight, cache-aware operations, that may be perfectly reasonable.
If each one performs additional complex queries or remote requests, the screen can become painfully slow.
Avoid N+1-style work
A common anti-pattern looks like:
retrieve posts
↓
for each post
run another expensive query
↓
for each column
run another expensive query
The amount of work grows with the number of visible rows.
For broader administration performance considerations, see Reducing WordPress Admin Server Load.
For database-specific considerations around posts and metadata, see Optimizing the WordPress Posts Table.
Do not perform remote API calls while rendering every row
A column that needs an external service should generally display previously synchronized or cached information.
A design such as:
render row
→ contact CRM API
→ wait
→ display result
repeated dozens of times can make the WordPress administration dependent on network latency and external availability.
Synchronize first, display second.
Test interactions with other plugins
SEO plugins, ecommerce systems, multilingual plugins and other administrative tools may add their own columns.
Your customization should therefore be tested on the real site rather than against a clean WordPress installation alone.
Check:
- column ordering;
- available horizontal space;
- Screen Options;
- responsive behavior;
- sorting;
- plugin-added columns;
- translated headings;
- large datasets.
A column layout that works with five Core columns may become much less practical after three plugins add another six.
A complete custom admin-column example
Suppose a project custom post type needs:
- Client;
- Status;
- Deadline;
- a small featured image.
We also want to remove Comments and keep Date at the end.
A complete implementation could look like this:
<?php
/**
* Customize Project columns.
*/
function project_customize_columns( $columns ) {
$new_columns = array();
if ( isset( $columns['cb'] ) ) {
$new_columns['cb'] =
$columns['cb'];
}
$new_columns['project_thumbnail'] =
__( 'Image', 'project' );
if ( isset( $columns['title'] ) ) {
$new_columns['title'] =
__( 'Project', 'project' );
}
$new_columns['project_client'] =
__( 'Client', 'project' );
$new_columns['project_status'] =
__( 'Status', 'project' );
$new_columns['project_deadline'] =
__( 'Deadline', 'project' );
if ( isset( $columns['author'] ) ) {
$new_columns['author'] =
$columns['author'];
}
if ( isset( $columns['date'] ) ) {
$new_columns['date'] =
$columns['date'];
}
return $new_columns;
}
add_filter(
'manage_project_posts_columns',
'project_customize_columns'
);
/**
* Render Project column values.
*/
function project_render_columns(
$column,
$post_id
) {
switch ( $column ) {
case 'project_thumbnail':
if ( has_post_thumbnail( $post_id ) ) {
echo get_the_post_thumbnail(
$post_id,
array( 50, 50 )
);
} else {
echo '—';
}
break;
case 'project_client':
$client = get_post_meta(
$post_id,
'client_name',
true
);
echo '' !== $client
? esc_html( $client )
: '—';
break;
case 'project_status':
$status = get_post_meta(
$post_id,
'project_status',
true
);
echo '' !== $status
? esc_html( $status )
: '—';
break;
case 'project_deadline':
$deadline = get_post_meta(
$post_id,
'project_deadline',
true
);
if ( '' === $deadline ) {
echo '—';
break;
}
$timestamp = strtotime( $deadline );
echo false !== $timestamp
? esc_html(
wp_date(
get_option( 'date_format' ),
$timestamp
)
)
: esc_html( $deadline );
break;
}
}
add_action(
'manage_project_posts_custom_column',
'project_render_columns',
10,
2
);
/**
* Register sortable Project columns.
*/
function project_sortable_columns( $columns ) {
$columns['project_client'] =
'project_client';
$columns['project_deadline'] =
'project_deadline';
return $columns;
}
add_filter(
'manage_edit-project_sortable_columns',
'project_sortable_columns'
);
/**
* Handle Project metadata sorting.
*/
function project_column_ordering( $query ) {
if (
! is_admin()
|| ! $query->is_main_query()
|| 'project' !==
$query->get( 'post_type' )
) {
return;
}
$orderby = $query->get( 'orderby' );
if ( 'project_client' === $orderby ) {
$query->set(
'meta_key',
'client_name'
);
$query->set(
'orderby',
'meta_value'
);
}
if ( 'project_deadline' === $orderby ) {
$query->set(
'meta_key',
'project_deadline'
);
$query->set(
'orderby',
'meta_value'
);
}
}
add_action(
'pre_get_posts',
'project_column_ordering'
);
This implementation keeps the responsibilities separate:
column filter
→ structure
custom column action
→ values
sortable column filter
→ sortable headings
pre_get_posts
→ ordering behavior
That separation makes the code considerably easier to maintain than one large callback trying to control every part of the list table.
Using TheOneWP Custom Content Columns
Manual PHP gives developers precise control over WordPress list tables, but not every project needs a custom-coded implementation for each content type.
TheOneWP Custom Content Columns provides a configurable way to expose useful information in supported WordPress content lists while retaining the familiar native administration workflow.
The goal is not to replace WordPress’s list tables.
It is to make them more useful:
native WordPress list table
+
relevant content information
=
better administration workflow
This is particularly valuable on sites where editors repeatedly open individual items only to inspect a small piece of information that could be visible directly in the list.
Use code when the behavior is application-specific
A custom implementation remains appropriate when columns require:
- complex business logic;
- special permissions;
- custom data sources;
- special rendering;
- advanced sorting;
- deep integration with application functionality.
For straightforward content-management customization, a configurable system can reduce repeated boilerplate and make the interface easier to maintain.
Preserve WordPress conventions
Whether columns are created manually or through a management feature, the strongest implementations continue to work with native concepts such as:
- Screen Options;
- primary columns;
- row actions;
- pagination;
- search;
- sorting;
- responsive list-table behavior.
Extending familiar WordPress patterns usually creates a more predictable experience than replacing the entire screen for the sake of a few additional values.
WordPress admin list column checklist
- Identify the exact post type whose list table needs customization.
- Use the appropriate post-type-specific column filter where possible.
- Use unique internal keys for custom columns.
- Remove columns through the column array rather than CSS when they should genuinely disappear.
- Rename existing columns only when the new label better reflects the content model.
- Order columns according to the user’s workflow.
- Keep the primary content column readable.
- Use the correct custom-column action for the content type.
- Escape manually rendered values according to their output context.
- Provide understandable fallbacks for missing data.
- Do not expose sensitive information unnecessarily.
- Use custom fields as one possible data source, not the only one.
- Preserve Screen Options when user-controlled visibility is useful.
- Understand the difference between hiding and removing a column.
- Be careful when changing the primary column.
- Make columns sortable only when ordering provides genuine value.
- Implement the underlying query behavior required for custom sorting.
- Use the correct sorting strategy for text, numbers and dates.
- Avoid expensive per-row queries.
- Avoid remote API requests during list-table rendering.
- Test realistic numbers of rows per page.
- Test with plugins that add their own columns.
- Test responsive administration layouts.
- Test translated column labels when the site is multilingual.
- Keep administrative functionality outside the frontend theme when it should survive theme changes.
Related guides
- Adding a Custom Field as a Column in WordPress
- WordPress Screen Options vs. Custom Columns
- WordPress Admin List Tables, Explained
- WordPress List-Table Sorting, Explained
- Standardizing the WordPress Admin for Teams
- Reducing WordPress Admin Confusion for Clients
Final recommendation
WordPress admin list columns should be treated as part of the content-management interface rather than as a place to display every piece of data associated with a post.
The Core architecture gives developers several distinct controls:
column filters
→ define structure
custom column actions
→ render values
Screen Options
→ control user visibility
primary column
→ controls key list-table behavior
sortable column filters
→ expose ordering
query hooks
→ implement custom ordering
Use those layers independently rather than trying to solve every requirement with CSS or a single callback.
Start by deciding what users genuinely need to know while scanning the list. Remove irrelevant information, rename generic headings when the content model requires it, add only high-value columns and place those columns in a logical order.
When a custom field needs to appear in the table, use the normal WordPress metadata APIs and the appropriate column hooks. When a column needs sorting, treat that as a separate query problem rather than assuming a visible value is automatically sortable.
Performance should remain part of the design. Every custom column callback can run for every visible row, so lightweight and cache-aware implementations scale much better than repeated database queries or live external requests.
For custom-coded applications, WordPress’s native hooks provide precise control. For configurable administration workflows, TheOneWP Custom Content Columns can expose useful information directly in supported content lists without replacing the native WordPress management experience.
The best admin table is therefore not the one with the most columns. It is the one that gives users the information they need to identify and manage content without forcing them to open every item individually.

