Sometimes an admin screen or editorial dashboard should show the current user’s drafts first, followed by drafts from everyone else. The same requirement can apply to published posts, pages, or a custom post type.
The archived version of this snippet had the right idea but used the wrong database column and placed get_current_user_id() inside a quoted SQL string. WordPress stores the author ID in wp_posts.post_author; there is no user_id column in that table. PHP functions also need to run before their values are passed to SQL.
This guide shows two safe approaches: a scoped WP_Query ordering filter for normal WordPress development and a prepared $wpdb query when you only need a small custom result set.
Confirm That a User Is Logged In
get_current_user_id() returns the authenticated user’s numeric ID or 0 for a visitor who is not logged in. An author-priority query usually belongs in an authenticated dashboard, plugin page, or AJAX/REST callback.
<?php
$currentUserId = get_current_user_id();
if ($currentUserId === 0) {
wp_die(esc_html__('You must be logged in to view this list.', 'buffernow'));
}
Authentication does not automatically grant access to every post. Check the capability required by the feature, such as edit_posts, before displaying drafts or private editorial data.
if (!current_user_can('edit_posts')) {
wp_die(esc_html__('You cannot view these posts.', 'buffernow'));
}
For AJAX and form requests, verify a WordPress nonce as well. A nonce helps prevent cross-site request forgery, but it does not replace the capability check.
Recommended: Add Scoped Ordering to WP_Query
WP_Query handles post types, statuses, caching, and object hydration. It does not provide a built-in order that says “current author first, then everyone else,” but the posts_orderby filter can add that expression.
The important part is scope. A global filter that changes every query can reorder menus, widgets, REST responses, and unrelated loops. Add the filter immediately before the custom query and remove it immediately afterward.
<?php
$currentUserId = get_current_user_id();
global $wpdb;
$orderCurrentAuthorFirst = static function (
string $orderBy,
WP_Query $query
) use ($currentUserId, $wpdb): string {
if (!$query->get('buffernow_current_author_first')) {
return $orderBy;
}
$authorPriority = $wpdb->prepare(
"CASE WHEN {$wpdb->posts}.post_author = %d THEN 0 ELSE 1 END",
$currentUserId
);
return $authorPriority . ", {$wpdb->posts}.post_modified_gmt DESC";
};
add_filter('posts_orderby', $orderCurrentAuthorFirst, 10, 2);
try {
$draftQuery = new WP_Query([
'post_type' => 'post',
'post_status' => 'draft',
'posts_per_page' => 25,
'buffernow_current_author_first' => true,
'no_found_rows' => true,
]);
} finally {
remove_filter('posts_orderby', $orderCurrentAuthorFirst, 10);
}
The CASE expression returns 0 for rows belonging to the current user and 1 for every other author. Ascending order puts zero first. The second sort, post_modified_gmt DESC, keeps the newest modified posts at the top inside each group.
no_found_rows avoids the extra total-count query when you do not need pagination. Remove it if the interface needs page counts.
Display the Results Safely
WP_Query returns normal post objects, so template functions and WordPress escaping helpers work as expected.
<?php if ($draftQuery->have_posts()) : ?>
<ul class="editorial-drafts">
<?php while ($draftQuery->have_posts()) : $draftQuery->the_post(); ?>
<li>
<a href="<?php echo esc_url(get_edit_post_link(get_the_ID())); ?>">
<?php echo esc_html(get_the_title() ?: __('(no title)', 'buffernow')); ?>
</a>
</li>
<?php endwhile; ?>
</ul>
<?php wp_reset_postdata(); ?>
<?php else : ?>
<p><?php echo esc_html__('No matching drafts found.', 'buffernow'); ?></p>
<?php endif; ?>
Escape URLs with esc_url() and visible text with esc_html(). Calling wp_reset_postdata() prevents the custom loop from affecting the main page query.
Use the Same Logic for Published Posts or Pages
Change post_status to publish when the list should contain public posts. Change post_type to page or a registered custom post type when required.
$projectQuery = new WP_Query([
'post_type' => 'project',
'post_status' => ['publish', 'draft'],
'posts_per_page' => 25,
'buffernow_current_author_first' => true,
]);
A custom post type must support authors for author-based sorting to be meaningful. Register it with author in its supports array and make sure your editorial interface actually assigns post_author.
Be careful when mixing public and non-public statuses. WordPress may remove inaccessible posts in some contexts, but a custom administrative endpoint should still enforce the appropriate capabilities explicitly.
Alternative: A Prepared $wpdb Query
Direct SQL is reasonable when you only need IDs and titles for a compact internal tool. Use WordPress table properties, placeholders, a result limit, and the correct post_author column.
<?php
$currentUserId = get_current_user_id();
$status = 'draft';
$postType = 'post';
$limit = 25;
global $wpdb;
$sql = $wpdb->prepare(
"SELECT ID, post_title, post_author, post_modified_gmt
FROM {$wpdb->posts}
WHERE post_status = %s
AND post_type = %s
ORDER BY
CASE WHEN post_author = %d THEN 0 ELSE 1 END,
post_modified_gmt DESC
LIMIT %d",
$status,
$postType,
$currentUserId,
$limit
);
$posts = $wpdb->get_results($sql);
Interpolating {$wpdb->posts} is safe here because WordPress supplies the trusted table name, including the site’s configured prefix. User-controlled values still go through $wpdb->prepare().
Do not write 'get_current_user_id()' inside SQL. MySQL does not execute PHP, and quoting the function name turns it into plain text. Also avoid hard-coding wp_posts, because multisite and custom installations may use another prefix.
Keep the Ordering Filter Predictable
A posts_orderby callback receives SQL for every query that runs while the filter remains attached. The custom query flag in the example gives the callback a clear exit condition, while the immediate remove_filter() call limits its lifetime. Use both safeguards rather than relying on only one.
Avoid anonymous callbacks that you cannot remove later. Store the closure in a variable, as shown above, so remove_filter() receives the same callable that add_filter() registered. The matching priority must also stay the same.
Plugins can alter queries through other filters, so test this code with the plugins active on the target site. If another callback replaces the complete ORDER BY clause, inspect the final SQL during development with a tool such as Query Monitor. Do not display raw query details on a production page.
The example deliberately returns its own two-part ordering expression. If your feature needs an additional tie-breaker, append a stable field such as ID DESC. A stable sort prevents items with identical modification times from changing position between requests.
Test the Current-User Ordering
Test with at least two accounts. Give both users matching posts, modify their posts at different times, and confirm that the signed-in user’s items always form the first group. Then confirm that the secondary date order works inside each group.
Also test these edge cases:
- A logged-out request returns no protected editorial list.
- A user without the required capability cannot view drafts.
- An author with no matching posts still sees allowed posts from other authors.
- Posts with the same modification time remain stable when you add an ID tie-breaker.
- Pages or custom post types use the intended author assignments.
Finally, inspect the generated SQL during development and confirm that it references the prefixed posts table and post_author. These checks catch the two original snippet errors before they reach production.
Common Mistakes to Avoid
The original short snippet exposes several mistakes worth checking in similar code:
- Use
post_author, notuser_id, in the posts table. - Execute
get_current_user_id()in PHP before preparing SQL. - Add a deterministic second sort inside each author group.
- Restrict post statuses and post types deliberately.
- Limit result size and paginate larger lists.
- Keep query filters scoped to one
WP_Queryinstance. - Check capabilities before exposing drafts.
- Escape titles and edit links during output.
If you only need the current user’s posts, use the simpler author argument in WP_Query. The conditional ordering technique is specifically for a combined list where the current user’s content appears first and other authors’ content remains visible afterward.
Final Result
For most plugins and themes, the scoped WP_Query filter is the maintainable choice. It preserves WordPress behavior while adding a small, explicit author-priority expression. A prepared $wpdb query remains useful for lightweight internal lists, provided it uses post_author, trusted table names, strict capabilities, placeholders, and safe output.