Opens in a new tab
  1. Home
  2. Guides
  3. Backend
Backend guide

Adding a Custom Field as a Column in WordPress

Learn how to add a WordPress custom field to an admin list-table column, retrieve and format post metadata, integrate with Screen Options, add sorting and avoid common performance problems.

  • Updated September 15, 2026
  • 22 min read
  • WordPress guide

Adding a custom field as a column in WordPress can make content management significantly faster when editors need to see important metadata without opening every post individually.

Suppose a website stores an internal status as a custom field:

project_status = approved

That value may be useful inside the individual post editor, but if an administrator needs to review fifty projects, opening every post simply to check its status is inefficient.

A custom administration column can turn the Posts or custom post type screen into something more useful:

Title | Project Status | Author | Date

The implementation has two main parts:

  1. register a new column in the WordPress list table;
  2. retrieve the custom field and render its value for each row.

From there, the column can also participate in WordPress Screen Options and, when appropriate, be made sortable.

This guide explains how to add a WordPress custom field as an admin column, how the relevant hooks work, how to retrieve post metadata safely, how to control the column position, how Screen Options interact with custom columns, how to make metadata columns sortable, and which performance problems to avoid on large administration screens.

Understand the relationship between custom fields and admin columns

A WordPress custom field and a WordPress admin column are not the same thing.

A custom field stores information associated with a post.

An admin column displays information inside a WordPress list table.

The relationship is:

Custom field
→ stores the value

Custom admin column
→ displays the value

WordPress commonly refers to post-level custom fields as post metadata.

For example, a Project post might have metadata such as:

client_name = Acme Ltd
project_status = approved
budget = 15000
deadline = 2026-10-15

These values are associated with the post but are not automatically shown as columns in Posts → All Posts or in the corresponding custom post type screen.

The WordPress get_post_meta() documentation describes the Core API used to retrieve metadata associated with a post.

A typical call looks like:

$status = get_post_meta(
    $post_id,
    'project_status',
    true
);

The third argument is important. Passing true asks WordPress for a single value rather than an array containing all values associated with that meta key.

Custom fields are stored independently from the main post record

Standard post properties such as the title, publication status and publication date belong to WordPress’s primary post model.

Arbitrary post metadata is stored through WordPress’s metadata system, commonly in wp_postmeta.

This flexibility allows themes and plugins to attach additional information without adding a new database column for every property.

For a broader look at the underlying content architecture, see WordPress Post Types vs. Custom Post Types.

The important point for this guide is simpler: the existence of a custom field does not automatically create an administration column for it.

You must explicitly decide which metadata deserves to appear in the list table.

Register the custom column in WordPress

WordPress provides filters specifically for changing the columns displayed by post list tables.

For a particular post type, the preferred pattern is:

manage_{$post_type}_posts_columns

The official WordPress documentation for manage_{$post_type}_posts_columns confirms that the filter receives the associative array of columns displayed for that post type.

The structure is essentially:

column_key => Column Label

Suppose we have a custom post type:

project

and each Project contains this custom field:

project_status

We can add a Status column with:

function project_add_admin_columns( $columns ) {

    $columns['project_status'] = __(
        'Status',
        'project'
    );

    return $columns;
}

add_filter(
    'manage_project_posts_columns',
    'project_add_admin_columns'
);

WordPress now knows that the Project list table contains a column identified by:

project_status

and displaying the heading:

Status

At this point, however, the cells are still empty.

Registering the column defines its existence. It does not tell WordPress what value should appear in each row.

Use a unique column key

The array key identifies the column throughout the implementation.

Avoid generic keys that may collide with built-in or plugin-provided columns.

Instead of:

status

a more specific identifier may be safer:

project_status

This also makes the relationship between the column and its underlying metadata easier to understand later.

Posts and pages have related hooks

WordPress also provides the general manage_posts_columns filter for post list tables.

Pages and hierarchical post types have some differences in the actions used to render column content, so do not blindly copy a Posts example into every possible WordPress object type.

When the customization belongs to one specific custom post type, the post-type-specific hooks usually make the intention clearer and reduce unnecessary processing on unrelated screens.

For the wider architecture behind these interfaces, read WordPress Admin List Tables, Explained.

Display the custom field value in the column

After registering the column, WordPress needs instructions for rendering its contents.

For a specific post type, the corresponding action follows this pattern:

manage_{$post_type}_posts_custom_column

The official WordPress documentation for custom post-type column output confirms that the callback receives both the column name and the current post ID.

For our Project example:

function project_render_admin_column(
    $column,
    $post_id
) {

    if ( 'project_status' !== $column ) {
        return;
    }

    $status = get_post_meta(
        $post_id,
        'project_status',
        true
    );

    echo esc_html( $status );
}

add_action(
    'manage_project_posts_custom_column',
    'project_render_admin_column',
    10,
    2
);

The list table can now display:

Website Redesign | Approved
SEO Audit        | Pending
New Landing Page | In Progress

The implementation follows a straightforward sequence:

WordPress renders row
        ↓
custom column callback runs
        ↓
post ID is available
        ↓
get_post_meta() retrieves field
        ↓
value is escaped
        ↓
value appears in table

Always check which column is being rendered

The callback can run for multiple custom columns.

That is why the code checks:

if ( 'project_status' !== $column ) {
    return;
}

If several custom columns are managed by one callback, a switch can be useful:

function project_render_admin_columns(
    $column,
    $post_id
) {

    switch ( $column ) {

        case 'project_status':

            $value = get_post_meta(
                $post_id,
                'project_status',
                true
            );

            echo esc_html( $value );

            break;

        case 'client_name':

            $value = get_post_meta(
                $post_id,
                'client_name',
                true
            );

            echo esc_html( $value );

            break;
    }
}

Escape values according to their output context

Metadata should not be printed into administration HTML without considering its contents.

For plain text:

echo esc_html( $value );

For a URL used in an HTML attribute:

echo esc_url( $value );

For an attribute value:

echo esc_attr( $value );

The WordPress data escaping documentation recommends escaping output as late as possible and using the function appropriate to the output context.

Administration screens are not exempt from output security simply because users need to log in before seeing them.

Handle empty and structured custom-field values properly

A real WordPress site rarely contains perfectly complete metadata.

Some older posts may not have the custom field at all.

Some may contain an empty value.

Others may contain IDs, arrays or serialized structures rather than human-readable strings.

Display a useful fallback for missing values

Instead of rendering an apparently broken empty cell:

if ( '' === $status ) {
    echo '—';
    return;
}

echo esc_html( $status );

The table now communicates that no value exists.

You could also use:

Not set

when that wording is more useful to editors.

The right fallback depends on the meaning of the field.

Do not confuse an empty value with every possible false-like value

The official get_post_meta() reference documents how scalar metadata values are returned.

For example, metadata stored as Boolean false may be represented as an empty string, while true is represented as '1'.

That means a simplistic empty check may not be appropriate when the custom field intentionally represents Boolean state.

If the metadata means:

featured = 0 or 1

the column might deliberately convert the stored value into a readable label:

$featured = get_post_meta(
    $post_id,
    'featured',
    true
);

echo $featured
    ? esc_html__( 'Yes', 'project' )
    : esc_html__( 'No', 'project' );

Convert stored values into useful display values

The database representation does not always belong in the interface.

Suppose the field contains:

project_status = awaiting_client_review

Editors probably do not need to see the raw storage key.

You could map it to:

Awaiting Client Review

Similarly:

  • a timestamp can become a formatted date;
  • a user ID can become a display name;
  • an attachment ID can become a thumbnail;
  • a post ID can become a related post title;
  • a Boolean value can become Yes or No;
  • a status key can become a readable label.

The purpose of the column is to make information easier to scan, not merely expose the database value.

Control where the custom field column appears

Appending a value to the end of the columns array places the new column near the end of the table.

That is acceptable for some fields but awkward for high-priority information.

Imagine:

Title | Author | Categories | Tags | Comments | Date | Status

If Status is one of the most important editorial properties, placing it after Date may not be ideal.

You can reconstruct the column array to position it deliberately.

For example:

function project_add_admin_columns( $columns ) {

    $new_columns = array();

    foreach ( $columns as $key => $label ) {

        $new_columns[ $key ] = $label;

        if ( 'title' === $key ) {
            $new_columns['project_status'] =
                __( 'Status', 'project' );
        }
    }

    return $new_columns;
}

The result becomes:

Title | Status | Author | Categories | Date

Column order is part of interface design

Place information according to how editors scan the screen.

A sensible sequence often moves from:

identity
→ important workflow information
→ classification
→ secondary information
→ date/status

For example:

Title
Client
Project Status
Deadline
Author
Date

may be much easier to work with than placing every custom field at the far right.

Do not add ten metadata columns merely because ten values exist. The list table should expose information that helps users make decisions without opening the item.

Custom field columns and WordPress Screen Options

One useful property of WordPress’s native column architecture is that custom columns can integrate with the existing Screen Options interface.

The WordPress documentation for custom post columns notes that columns added through the normal column filters automatically appear in Screen Options.

This creates a useful separation:

Developer
→ decides which columns exist

User
→ decides which available columns are visible

For example, a developer may register:

  • Client;
  • Project Status;
  • Deadline;
  • Budget.

A project manager may display all four.

An editor may only display Client and Status.

The site keeps a consistent information architecture without forcing every user to see the same amount of information.

This relationship is covered in detail in WordPress Screen Options vs. Custom Columns.

Do not hide columns with CSS when WordPress already has a visibility system

A rule such as:

.column-project_status {
    display: none;
}

only hides the rendered element visually.

It does not represent the user’s native column preference and can create inconsistencies with responsive behavior, accessibility and other administration customizations.

If the requirement is user-controlled visibility, use the WordPress column and Screen Options architecture instead of creating a parallel CSS-only mechanism.

Make the custom field column sortable when it adds real value

Displaying a custom field and sorting by it are separate features.

A column containing Deadline might be much more useful if an administrator can click its heading and order projects chronologically.

A column containing a long internal note probably should not be sortable at all.

WordPress provides the dynamic filter:

manage_{$screen_id}_sortable_columns

The WordPress sortable-columns reference documents this list-table mechanism.

For a Project custom post type, a basic registration could look like:

function project_sortable_columns( $columns ) {

    $columns['project_deadline'] =
        'project_deadline';

    return $columns;
}

add_filter(
    'manage_edit-project_sortable_columns',
    'project_sortable_columns'
);

This tells WordPress that the column heading can participate in ordering.

It does not, by itself, fully define how the underlying post query should sort the metadata.

Modify the administration query

For a simple text metadata value, the query can be adjusted with pre_get_posts:

function project_admin_column_order( $query ) {

    if ( ! is_admin() ) {
        return;
    }

    if ( ! $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_column_order'
);

The correct ordering strategy depends on the data type.

Text, numbers and dates may need different treatment.

For example, numeric values should generally not be treated as ordinary alphabetic strings.

Sorting these strings:

2
10
100

as plain text can produce an order that differs from numeric ordering.

WordPress queries provide options such as meta_value and meta_value_num depending on the stored data and desired behavior.

The WP_Query reference documents the available ordering and metadata-query parameters.

For a more complete explanation, see WordPress List-Table Sorting, Explained.

Not every metadata column should be sortable

Before adding sorting, ask whether anyone genuinely needs it.

Useful sortable metadata may include:

  • price;
  • deadline;
  • priority;
  • publication year;
  • numeric score;
  • external identifier.

Less useful candidates include:

  • large text fields;
  • decorative labels;
  • complex serialized structures;
  • values that require expensive computation.

A clickable heading is not automatically an improvement.

Performance matters when displaying metadata in large tables

Custom columns are rendered for every visible row.

A small inefficiency therefore becomes repetitive very quickly.

Consider a list table displaying:

100 posts
×
5 custom columns

If each callback performs expensive independent work, the administration screen can become unnecessarily slow.

This is particularly important when custom columns depend on:

  • complex metadata queries;
  • related posts;
  • taxonomy lookups;
  • external services;
  • large serialized structures;
  • uncached calculations.

For the wider database implications, see Optimizing the WordPress Posts Table.

For backend performance beyond list tables, see Reducing WordPress Admin Server Load.

Simple get_post_meta() usage is normally cache-aware

WordPress’s metadata APIs work with its metadata caching system, so developers should use the Core APIs rather than writing direct SQL simply to retrieve ordinary post metadata.

The correct first approach is generally:

get_post_meta()

rather than:

SELECT meta_value
FROM wp_postmeta
WHERE ...

Direct SQL also introduces unnecessary assumptions about database prefixes, caching and WordPress’s metadata behavior.

Never make remote requests per table row

A particularly expensive design would be:

for every post
→ call external API
→ wait for response
→ render column

If an external value needs to appear in an administration column, consider synchronizing or caching it before the list table is rendered.

The administration screen should display information, not become a batch-processing interface every time somebody opens it.

Be careful when sorting large datasets by metadata

Displaying metadata and querying or sorting large datasets by metadata have different performance characteristics.

WordPress’s flexible key-value metadata model is useful, but repeatedly asking the database to sort or filter very large collections by arbitrary metadata can become expensive.

If a value becomes fundamental to a high-volume application, evaluate the data model rather than assuming every business property should live indefinitely as arbitrary post meta.

A complete practical implementation

Suppose a WordPress site contains a project custom post type with these metadata fields:

client_name
project_status
project_deadline

We want the administration table to become:

Title
Client
Status
Deadline
Author
Date

The following example registers and renders all three columns:

<?php

/**
 * Register Project admin columns.
 */
function project_admin_columns( $columns ) {

    $new_columns = array();

    foreach ( $columns as $key => $label ) {

        $new_columns[ $key ] = $label;

        if ( 'title' === $key ) {

            $new_columns['client_name'] =
                __( 'Client', 'project' );

            $new_columns['project_status'] =
                __( 'Status', 'project' );

            $new_columns['project_deadline'] =
                __( 'Deadline', 'project' );
        }
    }

    return $new_columns;
}

add_filter(
    'manage_project_posts_columns',
    'project_admin_columns'
);


/**
 * Render Project admin columns.
 */
function project_admin_column_content(
    $column,
    $post_id
) {

    switch ( $column ) {

        case 'client_name':

            $client = get_post_meta(
                $post_id,
                'client_name',
                true
            );

            echo '' !== $client
                ? esc_html( $client )
                : '&mdash;';

            break;


        case 'project_status':

            $status = get_post_meta(
                $post_id,
                'project_status',
                true
            );

            echo '' !== $status
                ? esc_html( $status )
                : '&mdash;';

            break;


        case 'project_deadline':

            $deadline = get_post_meta(
                $post_id,
                'project_deadline',
                true
            );

            if ( '' === $deadline ) {
                echo '&mdash;';
                break;
            }

            $timestamp = strtotime( $deadline );

            if ( false === $timestamp ) {
                echo esc_html( $deadline );
                break;
            }

            echo esc_html(
                wp_date(
                    get_option( 'date_format' ),
                    $timestamp
                )
            );

            break;
    }
}

add_action(
    'manage_project_posts_custom_column',
    'project_admin_column_content',
    10,
    2
);


/**
 * Register sortable columns.
 */
function project_admin_sortable_columns(
    $columns
) {

    $columns['client_name'] =
        'client_name';

    $columns['project_deadline'] =
        'project_deadline';

    return $columns;
}

add_filter(
    'manage_edit-project_sortable_columns',
    'project_admin_sortable_columns'
);


/**
 * Apply metadata ordering.
 */
function project_admin_column_ordering(
    $query
) {

    if (
        ! is_admin()
        || ! $query->is_main_query()
        || 'project' !==
            $query->get( 'post_type' )
    ) {
        return;
    }

    $orderby = $query->get( 'orderby' );

    if ( 'client_name' === $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_admin_column_ordering'
);

This example demonstrates the four distinct responsibilities involved:

  1. register the columns;
  2. retrieve and display the metadata;
  3. declare selected columns sortable;
  4. modify the underlying query when sorting is requested.

Keeping those responsibilities separate makes the implementation easier to understand and maintain.

Use a plugin for functionality that should survive theme changes

If these columns describe the site’s content model, they generally should not disappear because somebody changes the frontend theme.

That makes a site-specific plugin, custom functionality plugin or modular administration system a better home than a theme’s functions.php in many projects.

The interface is part of content administration, not necessarily presentation.

When to use a custom field column and when not to

A custom field deserves a list-table column when seeing the value helps users manage multiple records efficiently.

Good candidates include:

  • workflow status;
  • client;
  • deadline;
  • SKU;
  • supplier;
  • price;
  • event date;
  • assigned team member;
  • external ID;
  • publication year;
  • approval state.

Less suitable values include:

  • long descriptions;
  • private credentials;
  • sensitive personal information;
  • large serialized arrays;
  • values that require expensive live calculations;
  • information that editors almost never need while browsing the list.

Do not expose sensitive metadata simply because it exists

An administration column makes a value easier to see.

That is precisely why sensitive values require additional care.

Do not casually expose:

  • API keys;
  • authentication tokens;
  • private customer data;
  • internal security information;
  • credentials;
  • secret integration identifiers.

Always consider which roles can access the screen and whether the value should be visible to them.

Consider a taxonomy when the value is really classification

Not every property belongs in post meta.

If the value represents reusable classification such as:

Industry
Region
Product Type
Genre
Department

a WordPress taxonomy may provide a better content model, especially when editors need to browse, filter or archive content by that classification.

If the value is a property of one specific object, such as:

Price
Deadline
Reference Number
External ID

metadata is often more natural.

The storage model should be chosen before the administration column is designed.

Using TheOneWP Custom Content Columns

Building admin columns manually is useful when the behavior is highly specific or belongs to custom application logic.

But many WordPress sites simply need a practical way to expose useful content information in existing administration screens.

TheOneWP Custom Content Columns is designed for that use case.

Instead of replacing WordPress’s administration tables, the feature extends the existing workflow with additional useful information.

The architectural goal remains:

WordPress content
+
useful metadata
+
native admin list
=
faster content management

This approach is especially useful when editors repeatedly open posts simply to inspect values that could be visible directly from the list.

Keep WordPress’s native list-table behavior where possible

A custom column should ideally continue to coexist with the features administrators already understand:

  • Screen Options;
  • search;
  • pagination;
  • row actions;
  • bulk actions;
  • filters;
  • sorting where appropriate.

That is usually preferable to replacing the entire content-management screen with a proprietary table just to expose a few additional fields.

TheOneWP approach can therefore complement WordPress rather than forcing editors to learn a completely separate administration model.

Custom field column checklist

  • Identify the exact metadata key before writing the column code.
  • Confirm that the value is actually stored as post metadata.
  • Use the post-type-specific column filter where appropriate.
  • Give the column a unique internal key.
  • Use a clear human-readable column label.
  • Render the value through the appropriate custom-column action.
  • Retrieve ordinary post metadata with get_post_meta().
  • Pass true when a single metadata value is expected.
  • Escape output according to its HTML context.
  • Provide a useful fallback for missing values.
  • Convert machine-oriented values into readable labels where useful.
  • Position important columns deliberately.
  • Do not overload the list table with unnecessary metadata.
  • Preserve WordPress Screen Options where user-controlled visibility is useful.
  • Do not use CSS hiding as a substitute for native column visibility.
  • Only make a column sortable when sorting provides real workflow value.
  • Use the correct ordering method for text, numeric and date values.
  • Avoid expensive calculations inside every row callback.
  • Never perform remote API requests for every visible row.
  • Test with realistic numbers of posts per page.
  • Test with other plugins that add admin columns.
  • Check narrow and responsive administration layouts.
  • Do not expose sensitive metadata unnecessarily.
  • Keep content-model functionality independent from the frontend theme where appropriate.

Related guides

Final recommendation

Adding a custom field as a WordPress admin column is most useful when metadata needs to become visible at the point where editors manage multiple pieces of content.

The basic implementation is straightforward:

register column
        ↓
receive post ID
        ↓
retrieve post meta
        ↓
format value
        ↓
escape output
        ↓
render column

But a good implementation goes beyond simply printing get_post_meta().

Choose metadata that genuinely helps users make decisions from the list screen. Position important columns deliberately, provide understandable fallbacks for missing data, convert storage-oriented values into readable labels and preserve WordPress’s native Screen Options where personal visibility controls are useful.

If sorting is required, treat it as a separate feature. Registering a custom column does not automatically make its metadata sortable, and making a heading clickable does not automatically make the underlying query understand the field.

Performance also deserves attention. A column callback may run dozens or hundreds of times during a single page request, so avoid expensive per-row calculations and never treat the administration table as an excuse to perform repeated remote requests.

For highly specific application logic, WordPress’s native column and metadata APIs provide precise developer control. For more configurable administration workflows, TheOneWP Custom Content Columns can expose useful content information directly within supported WordPress list screens.

The goal is not to display every custom field WordPress stores. It is to expose the small set of values that makes managing content faster, clearer and less repetitive.

Simplify your WordPress stack

A modular WordPress toolkit. 104 focused tools.

Ultimately, you can build cleaner workflows, maintain fewer plugins and enable only the features each website actually needs.