WordPress list-table sorting is the system that lets users click certain column headings in wp-admin to change the order in which records are displayed.
You see this behavior throughout WordPress administration.
Depending on the screen, you may be able to sort by:
- title;
- author;
- date;
- username;
- email address;
- comment author;
- custom metadata;
- plugin-defined values.
A sortable column usually appears with a clickable heading and an ascending or descending indicator.
But WordPress list-table sorting has two distinct layers:
1. Tell the interface
that a column is sortable
2. Tell the underlying query
how that value should actually be sorted
Those two steps are related, but they are not the same thing.
A developer can make a column heading appear sortable without correctly modifying the underlying query. The interface may then generate an orderby request that the query does not understand.
This guide explains how WordPress list-table sorting works, how sortable columns are registered, how orderby and order are passed through administration requests, how to sort custom metadata correctly, why numeric and textual sorting produce different results and how to avoid expensive or misleading custom sorting implementations.
What is a WordPress list table?
WordPress administration repeatedly uses tabular interfaces for collections of objects.
Examples include:
- Posts;
- Pages;
- Users;
- Comments;
- Plugins;
- Media in List view;
- taxonomies;
- custom post types;
- plugin-defined management screens.
These interfaces commonly provide:
- columns;
- row actions;
- bulk actions;
- filters;
- search;
- pagination;
- Screen Options;
- sorting.
For the broader architecture, see WordPress Admin List Tables, Explained.
What makes a column sortable?
A normal WordPress column may simply display information.
For example:
Title
Featured Image
Author
SEO Score
Price
Only some of those columns may be sortable.
A sortable column tells WordPress:
This heading can generate
an ordering request.
The underlying list-table system then creates URLs containing query parameters such as:
orderby=title
order=asc
or:
orderby=date
order=desc
The orderby parameter identifies what should be sorted
Consider an administration URL such as:
/wp-admin/edit.php?
post_type=post
&orderby=title
&order=asc
The important parameters are:
orderby
order
orderby identifies the value used for sorting.
order determines the direction.
ASC and DESC control the direction
The two normal values are:
ASC
DESC
Conceptually:
ASC
→ A to Z
→ smallest to largest
→ oldest to newest
DESC
→ Z to A
→ largest to smallest
→ newest to oldest
The exact meaning depends on the data type being sorted.
WordPress uses WP_List_Table for much of this interface
Core’s:
WP_List_Table
class contains the infrastructure used by many WordPress administration tables.
The official WP_List_Table reference documents the base list-table class.
One of its methods is:
get_sortable_columns()
The official WP_List_Table::get_sortable_columns() documentation describes the format used to define sortable columns.
A sortable column maps a visible column to an orderby value
The simplest structure is conceptually:
'column_name' => 'orderby_value'
For example:
'title' => 'title'
This means:
visible column:
title
query ordering value:
title
The two names do not necessarily have to be identical.
WordPress exposes sortable columns through a screen-specific filter
Core applies a dynamic filter with the pattern:
manage_{$screen_id}_sortable_columns
The official sortable-columns hook documentation explains that the dynamic part is the current WordPress screen ID.
For the normal Posts screen, the screen ID is:
edit-post
so the filter becomes:
manage_edit-post_sortable_columns
Custom post types get their own screen IDs
Suppose you register:
post_type = property
The list-table screen ID is normally:
edit-property
and the sortable-column filter becomes:
manage_edit-property_sortable_columns
This lets developers customize sorting independently for different content types.
Example: register a custom sortable column
Suppose a custom post type contains a column:
Price
with the internal column key:
property_price
You can register it as sortable:
function myplugin_sortable_property_columns(
$columns
) {
$columns['property_price'] =
'property_price';
return $columns;
}
add_filter(
'manage_edit-property_sortable_columns',
'myplugin_sortable_property_columns'
);
This tells the list table that:
property_price
can be used as an ordering choice.
Registering the column is only the first half
This is the critical part.
WordPress can now generate:
orderby=property_price
but WP_Query does not automatically know what:
property_price
means.
You still need to map that custom ordering value to something the query understands.
Core fields often work without additional query logic
WP_Query already supports many standard ordering values.
The official WP_Query documentation includes supported values such as:
date
title
name
author
modified
parent
ID
menu_order
comment_count
meta_value
meta_value_num
Therefore, a column that maps directly to a supported Core value may require little additional query work.
Custom metadata needs additional handling
Suppose:
property_price
is stored as post meta:
meta_key = property_price
You need to modify the administration query when:
orderby=property_price
is requested.
pre_get_posts is commonly used for post list sorting
WordPress provides:
pre_get_posts
before a WP_Query query executes.
The official pre_get_posts documentation describes how the query object can be modified before SQL is generated.
Example: sort a custom metadata column
function myplugin_property_admin_sorting(
$query
) {
if ( ! is_admin() ) {
return;
}
if (
'property' !==
$query->get( 'post_type' )
) {
return;
}
if (
'property_price' !==
$query->get( 'orderby' )
) {
return;
}
$query->set(
'meta_key',
'property_price'
);
$query->set(
'orderby',
'meta_value_num'
);
}
add_action(
'pre_get_posts',
'myplugin_property_admin_sorting'
);
Now the workflow becomes:
User clicks Price
↓
orderby=property_price
↓
pre_get_posts detects it
↓
meta_key=property_price
↓
orderby=meta_value_num
↓
WP_Query builds the correct ordering
Why meta_value_num matters for numbers
Metadata is commonly stored as text at the database level.
If you sort numeric-looking values as strings, you may get:
1
10
100
2
20
3
instead of:
1
2
3
10
20
100
For numeric metadata, use:
meta_value_num
where appropriate.
The official WP_Query documentation specifically distinguishes meta_value from meta_value_num.
Text sorting and numeric sorting are not interchangeable
Use:
meta_value
for textual values such as:
Bronze
Gold
Premium
Silver
Use:
meta_value_num
for numeric values such as:
15
49
299
1250
when those values are actually stored as numeric-compatible metadata.
Dates stored in metadata need deliberate formatting
A date such as:
14/09/2026
is convenient for humans.
It is less convenient for reliable database ordering.
A normalized representation such as:
20260914
or an appropriate database datetime format is generally easier to sort predictably.
The storage format should be chosen when designing the data model, not patched later in the list-table interface.
WP_Query supports meta_type too
Modern WP_Query can cast metadata using:
meta_type
with supported SQL types such as:
NUMERIC
CHAR
DATE
DATETIME
DECIMAL
SIGNED
UNSIGNED
This can be useful when metadata needs a particular comparison or ordering type.
Named meta_query clauses can also be sorted
WP_Query supports ordering by named clauses inside:
meta_query
For more complex queries, a clause can be given a key and then referenced through:
orderby
This provides more flexibility than a single global:
meta_key
when several metadata conditions participate in the query.
Sorting by metadata can be substantially more expensive
A Core field such as:
post_date
exists directly in:
wp_posts
A custom property such as:
property_price
may live in:
wp_postmeta
Sorting by it can therefore require additional joins and database work.
Do not make every custom column sortable automatically
A sortable column should provide enough value to justify the query cost.
Useful candidates might include:
- price;
- priority;
- event date;
- registration date;
- last activity;
- numeric score.
Less useful candidates may include:
- large formatted descriptions;
- remote API status;
- generated HTML;
- computed values requiring many queries;
- values without a meaningful natural order.
The visible value and sortable value can be different
Suppose a column displays:
€1,250.00
The interface should not attempt to sort the formatted string:
€1,250.00
Instead, the underlying query should sort the raw value:
1250
Formatting belongs to presentation.
Sorting belongs to the data layer.
Never sort by the HTML rendered in the column
A list-table callback may output:
<strong>High Priority</strong>
but that rendered HTML is not what WordPress should use for SQL ordering.
The correct model is:
Database value
→ query ordering
Formatted output
→ visual presentation
Sorting happens before rows are rendered
The database query determines which rows arrive and in what order.
Only afterward does WordPress render each row and its columns.
Conceptually:
Request
↓
Query parameters
↓
Database ordering
↓
Result set
↓
List-table rendering
You cannot reliably fix database ordering inside a column-rendering callback because the result set has already been retrieved.
WordPress can define an initial sorting direction
The sortable-column definition can contain additional information beyond:
'column' => 'orderby'
The official WP_List_Table::get_sortable_columns() documentation supports arrays that can define properties such as an initial sort direction.
Depending on the definition, the initial interaction can prefer:
ASC
or:
DESC
Choose the default direction based on the meaning of the data
For alphabetical names:
ASC
is often natural.
For dates:
DESC
is often more useful because recent records appear first.
For revenue:
DESC
may surface the largest values first.
The default should match the user’s likely question.
Do not hardcode $_GET directly into SQL
A sorting request ultimately originates from URL parameters.
Never take:
$_GET['orderby']
and concatenate it directly into a SQL statement.
A request such as:
?orderby=...
is user-controlled input.
Prefer recognized query values
A safer architecture is:
requested alias
↓
whitelist / map
↓
known query parameter
For example:
property_price
→ meta_key property_price
→ meta_value_num
rather than allowing arbitrary SQL identifiers to arrive from the URL.
Custom SQL requires separate security handling
If a custom list table uses raw SQL instead of WP_Query, validate:
- allowed ordering fields;
- allowed directions;
- table names;
- dynamic values.
For example:
$allowed_orderby = array(
'name',
'date_created',
'priority',
);
$orderby = 'date_created';
if (
isset( $_GET['orderby'] )
&&
in_array(
$_GET['orderby'],
$allowed_orderby,
true
)
) {
$orderby =
$_GET['orderby'];
}
Direct database access should follow the broader practices described in How to Safely Run SQL Queries in WordPress.
WP_List_Table custom implementations need their own query logic
If you build a custom class extending:
WP_List_Table
you commonly override:
get_columns()
get_sortable_columns()
prepare_items()
The sortable definition controls the interface.
Your:
prepare_items()
implementation still needs to apply the requested order to the data source.
Example custom WP_List_Table sortable definition
protected function get_sortable_columns() {
return array(
'name' => array(
'name',
false
),
'created' => array(
'created_at',
true
),
);
}
This tells WordPress which column headings should behave as sorting controls.
Then prepare_items() must honor those values
A custom implementation might retrieve:
orderby
order
and map them to a safe, known database query.
The exact code depends on the data source.
The key principle is:
UI definition
and
query implementation
must agree.
Sorting WordPress users follows a different query API
Not every administration screen uses WP_Query.
The Users screen uses the WordPress user-query system.
Custom sorting there may require:
WP_User_Query
and user-specific hooks.
This is why generic code that assumes every list table is powered by posts can fail on other wp-admin screens.
Comments have their own list-table sorting definitions
WordPress Core’s comments list table defines sortable columns such as:
author
response
date
internally mapping those visible columns to values such as:
comment_author
comment_post_ID
comment_date
This illustrates the separation between:
visible column key
and
underlying sort value
Sorting can interact with filters
An administration list may already be filtered by:
- post status;
- category;
- taxonomy;
- date;
- author;
- custom metadata;
- search term.
Sorting should normally reorder that filtered result set rather than discard the filtering state.
Preserve the current list-table context
When WordPress generates sortable headings, it preserves relevant query parameters so a user can move from:
Published posts
sorted by date
to:
Published posts
sorted by title
without unexpectedly returning to all content.
Custom interfaces should follow the same principle.
Sorting and pagination must agree
Suppose the list contains:
10,000 records
with:
50 per page
The sorting must be performed at the database-query level before pagination.
The correct sequence is:
filter
↓
sort
↓
paginate
↓
render
not:
fetch page 1
↓
sort those 50 rows in PHP
Client-side sorting is not equivalent to list-table sorting
JavaScript could sort the rows already rendered in the browser.
But that only reorders:
the current page
rather than:
the complete dataset
For a paginated WordPress administration list, server-side sorting is normally the correct model.
Sorting by custom post meta can exclude records unexpectedly
This deserves testing.
Some metadata-based query patterns can affect posts that do not contain the requested meta key.
For example:
Post A
property_price = 100
Post B
property_price = 200
Post C
no property_price metadata
Depending on the query construction, Post C may not participate in the same way as A and B.
Before deploying custom metadata sorting, test:
- records with values;
- records with zero;
- records with empty values;
- records where the meta key does not exist.
Null, empty and missing values need a policy
Decide where these should appear:
missing
empty
0
null-like value
For example:
priced properties first
unpriced properties last
may require more sophisticated query logic than a basic meta_value_num order.
Natural business order may not be alphabetical order
Imagine a workflow status:
Critical
High
Medium
Low
Alphabetical sorting produces:
Critical
High
Low
Medium
which does not represent the business priority.
In that case you may need:
- a numeric priority value;
- a separate sort key;
- custom SQL ordering;
- a purpose-built data model.
Store sortable values in sortable formats
If a field will be used frequently for sorting, design the storage accordingly.
Examples:
Price
→ numeric value
Date
→ normalized date representation
Priority
→ integer rank
Status
→ explicit sortable rank if business order matters
Do not store everything as human-facing formatted strings and then expect efficient database sorting later.
Custom database tables can be better for specialized datasets
If an application needs to sort and filter millions of structured records by many different fields, generic WordPress metadata may not be the ideal data model.
Custom tables can provide:
- dedicated typed columns;
- appropriate indexes;
- purpose-built query patterns;
- more predictable ordering.
The database model should follow the application requirements rather than forcing every dataset through wp_postmeta.
Indexes influence sorting performance
Sorting large result sets can become expensive when the database cannot efficiently locate and order the relevant values.
Performance depends on:
- table size;
- filter conditions;
- joins;
- indexes;
- selected ordering field;
- number of rows returned.
A slow sortable column should be profiled as a database-query problem rather than fixed by hiding the column.
Do not add indexes without measuring the query
A good process is:
identify slow request
↓
inspect generated query
↓
inspect execution plan
↓
review existing indexes
↓
test proposed change
Adding indexes to random metadata columns without understanding the query can increase storage and write cost without solving the relevant bottleneck.
Sorting should be deterministic where possible
Suppose several records have the same:
price = 100
If no secondary ordering exists, their internal order may not be meaningful.
For predictable interfaces, it can be useful to add a secondary order such as:
price ASC
title ASC
or:
priority DESC
date DESC
WP_Query supports multiple orderby values
WP_Query can accept an array of ordering rules in supported contexts.
Conceptually:
orderby = array(
'menu_order' => 'ASC',
'title' => 'ASC',
)
This can make ordering more stable when several records share the primary value.
Do not modify every admin query accidentally
This is one of the most common mistakes with:
pre_get_posts
A callback such as:
function modify_query(
$query
) {
$query->set(
'orderby',
'meta_value'
);
}
without appropriate guards can affect many unrelated queries.
Scope custom ordering carefully
Check relevant conditions such as:
is_admin()
post type
requested orderby
main query where appropriate
current screen when necessary
For example:
if ( ! is_admin() ) {
return;
}
if (
'property' !==
$query->get( 'post_type' )
) {
return;
}
if (
'property_price' !==
$query->get( 'orderby' )
) {
return;
}
Do not force a custom order when the user selected another one
If the user clicks:
Title
your plugin should not continue forcing:
Price
unless that behavior is deliberately part of the application.
A sorting callback should normally activate only for its own recognized orderby alias.
Default ordering and interactive sorting are separate
A custom post type may have a default administration order such as:
menu_order ASC
while still allowing users to click:
Title
Date
Price
A well-designed implementation distinguishes:
no explicit sort selected
→ use application default
explicit sortable column selected
→ respect user choice
Screen Options affect visibility, not sorting logic
A user may hide a sortable column through Screen Options.
That changes what they see.
It does not fundamentally redefine how the query system works.
For the relationship between list-table columns and personal visibility preferences, see WordPress Screen Options vs. Custom Columns and WordPress Screen Options, Explained.
Custom Content Columns can improve list-table information density
TheOneWP Custom Content Columns can customize the information displayed in supported WordPress content lists.
This solves a different problem from query sorting.
The distinction is:
Custom column
→ what information is visible
Sortable column
→ whether the dataset can be ordered by it
Query implementation
→ how that ordering actually happens
Adding a column should therefore not automatically imply that it should also become sortable.
Custom Sort Order solves another separate problem
TheOneWP Custom Sort Order addresses persistent manual content ordering.
That is different from clicking a list-table heading.
Compare:
List-table sorting
→ temporary viewing order
Custom content order
→ stored editorial order
For example, an administrator might permanently arrange:
Service A
Service B
Service C
for frontend presentation while temporarily sorting the wp-admin list alphabetically to locate a record.
Do not save list-table sorting as content order accidentally
Clicking:
Title ↑
should normally alter only the current administration view.
It should not rewrite:
menu_order;- post metadata;
- taxonomy order;
- frontend presentation order.
Sorting a view and changing application data are fundamentally different operations.
Test sorting with realistic data
A custom sortable column can appear correct with:
10 test records
and perform badly with:
100,000 production records
Testing should include:
- large datasets;
- duplicate values;
- missing values;
- zero values;
- long text;
- different roles;
- pagination;
- filters;
- search queries.
Verify both directions
Test:
ASC
and:
DESC
A column that appears correct in one direction can expose incorrect null handling or string comparison behavior in the other.
Verify the URL parameters
When clicking a sortable heading, inspect the administration URL.
You should see values such as:
orderby=property_price
order=asc
Then confirm the server-side query maps those values correctly.
Inspect the generated database query when debugging
If ordering behaves incorrectly, inspect the actual SQL rather than guessing from the visible rows.
Questions include:
- Which table is being ordered?
- Is
wp_postmetajoined? - Is the value cast numerically?
- Is the requested meta key correct?
- Are missing metadata rows excluded?
- Is another plugin changing
orderby?
Plugins can conflict over admin queries
Several plugins may hook into:
pre_get_posts
or lower-level SQL filters.
A sorting bug may therefore arise from:
- priority conflicts;
- one plugin overwriting another query variable;
- global query modifications;
- incorrect post-type checks;
- unexpected meta joins.
Use posts_orderby only when higher-level APIs are insufficient
WordPress exposes the:
posts_orderby
filter for modifying the generated SQL ORDER BY clause.
The official posts_orderby documentation explains that the filter runs before the post-retrieving SQL statement executes.
This is useful for advanced ordering logic.
But it operates closer to SQL and should not be the first choice when normal WP_Query parameters already express the required ordering.
Prefer the highest-level API that solves the problem
A reasonable order of preference is:
WP_Query orderby parameters
↓
meta query ordering
↓
pre_get_posts
↓
specialized SQL filters
↓
custom SQL
Move lower only when the higher-level API cannot represent the required behavior.
Sorting checklist for developers
- Identify the correct WordPress screen ID.
- Register only genuinely useful sortable columns.
- Use the appropriate
manage_{$screen_id}_sortable_columnsfilter. - Map the visible column to a known internal
orderbyvalue. - Implement the corresponding query logic.
- Use Core ordering values when possible.
- Use
meta_valuefor textual metadata where appropriate. - Use
meta_value_numfor numeric metadata. - Consider
meta_typewhen explicit casting is needed. - Store sortable data in appropriate formats.
- Do not sort formatted HTML.
- Do not concatenate arbitrary
orderbyvalues into SQL. - Whitelist raw-SQL ordering fields.
- Scope
pre_get_postscallbacks carefully. - Respect explicit user sorting choices.
- Keep default ordering separate from interactive sorting.
- Test missing metadata.
- Test duplicate values.
- Test ASC and DESC.
- Test pagination.
- Test filters and search together with sorting.
- Profile metadata sorting on realistic datasets.
- Inspect SQL when results are unexpected.
- Use lower-level SQL filters only when necessary.
Sorting checklist for site administrators
- Click column headings only when they display sorting indicators.
- Check whether the current screen supports sorting for the field you need.
- Remember that sorting changes the current view, not the stored content order.
- Use filters before sorting large datasets when possible.
- Keep useful sortable columns visible through Screen Options.
- Report columns that visually appear sortable but return incorrect results.
- Check whether numeric values are being sorted numerically.
- Check whether records with missing values disappear unexpectedly.
- Do not assume slow sorting means the entire WordPress database is slow.
Related guides
- WordPress Admin List Tables, Explained
- WordPress Screen Options vs. Custom Columns
- WordPress Screen Options, Explained
- WordPress Post Types vs. Custom Post Types
- Optimizing the WordPress Posts Table
- How to Safely Run SQL Queries in WordPress
Final recommendation
WordPress list-table sorting is best understood as a collaboration between the administration interface and the query behind it.
The complete flow is:
Register column
↓
Mark column as sortable
↓
WordPress generates orderby + order
↓
Query interprets the requested value
↓
Database sorts the result set
↓
Pagination is applied
↓
List table renders the rows
Registering a sortable heading is therefore only half the implementation.
If the value already maps to a supported WordPress query field such as title, date or ID, Core can often perform the ordering directly. If the column represents metadata or another custom value, the query must explicitly map the requested orderby value to the appropriate data source and comparison type.
Use numeric sorting for numbers, normalized representations for dates, meaningful secondary ordering where duplicate values are common and careful query scoping so custom administration behavior does not leak into unrelated screens.
Most importantly, keep three concepts separate:
Column display
→ what users see
List-table sorting
→ how the current view is ordered
Stored content order
→ persistent application data
When those layers remain separate, sortable WordPress administration columns become genuinely useful instead of merely decorative controls attached to queries that do not understand what was clicked.

