Drag-and-drop post ordering in WordPress gives editors a visual way to define a persistent sequence without manually entering numeric order values for every post.
Instead of editing:
Project A → 10
Project B → 20
Project C → 30
an editor can simply drag:
Project C
Project A
Project B
into the desired position and let WordPress store the corresponding order values.
This workflow can be useful for:
- services;
- portfolio projects;
- team members;
- testimonials;
- FAQ entries;
- documentation chapters;
- course lessons;
- featured content;
- custom post types with an editorial sequence.
But the visual interaction is only one part of the implementation.
A reliable drag-and-drop ordering system also needs to answer:
- where the order is stored;
- which users may change it;
- how the new sequence is submitted;
- how the request is protected;
- how hundreds of posts are handled;
- whether filters and pagination are compatible;
- whether the order should affect the front end.
A useful architecture is:
Drag interface
↓
ordered post IDs
↓
secure request
↓
persistent order values
↓
queries that deliberately use those values
This guide explains how that complete workflow works in WordPress, from menu_order and WP_Query to jQuery UI Sortable, AJAX, nonces, capabilities, pagination and frontend integration.
WordPress already has a field for persistent post order
Every WordPress post record is stored in the posts table.
One of the available columns is:
menu_order
The name can be misleading because it is not limited to WordPress navigation menus.
The official WP_Query documentation lists menu_order as a supported ordering field and explains that it can be used for any post type with distinct values.
Most posts simply begin with:
menu_order = 0
until something deliberately changes it.
Drag-and-drop is only the editing interface
The drag operation itself does not define a new WordPress data model.
It is simply a convenient way of translating:
visual position
into:
stored order value
For example:
visual position 1
→ menu_order 0
visual position 2
→ menu_order 1
visual position 3
→ menu_order 2
or:
visual position 1
→ menu_order 10
visual position 2
→ menu_order 20
visual position 3
→ menu_order 30
Both strategies can work.
Why use gaps such as 10, 20 and 30?
A sequence such as:
10
20
30
40
leaves room to insert values between existing items manually.
For example:
10
15
20
30
However, if the order is always recalculated after drag-and-drop, simple sequential values such as:
0
1
2
3
are often sufficient.
The important requirement is consistency.
menu_order is different from list-table sorting
This distinction matters immediately.
When an editor clicks:
Title ↑
or:
Date ↓
in a WordPress list table, the current administration query changes its sorting.
That normally does not modify the posts themselves.
Drag-and-drop custom ordering is different because it usually writes persistent values such as menu_order.
Compare:
Clickable list-table heading
→ temporary viewing order
Drag-and-drop custom order
→ persistent stored sequence
For the first system, see WordPress List-Table Sorting, Explained.
The wp-admin list table is a natural place for drag-and-drop
WordPress already gives editors familiar list screens for Posts, Pages and custom post types.
Those screens provide:
- rows representing posts;
- filters;
- search;
- bulk actions;
- pagination;
- Screen Options;
- custom columns;
- sortable headings.
Adding drag handles to this existing interface can be more intuitive than building an entirely separate ordering screen.
The wider administration architecture is covered in WordPress Admin List Tables, Explained.
Not every post type needs manual ordering
Before implementing drag-and-drop, ask whether sequence has actual meaning.
Good candidates include:
service
team_member
testimonial
portfolio
lesson
faq
Less obvious candidates include ordinary blog Posts, where users generally expect:
newest first
A manual ordering system should solve a content-model problem rather than merely add another control to wp-admin.
Custom post types are particularly suitable
Custom post types often represent structured collections whose position is part of their meaning.
For example:
Service 1
Service 2
Service 3
or:
Lesson 1
Lesson 2
Lesson 3
For background on when a separate content structure is appropriate, see WordPress Post Types vs. Custom Post Types.
Query posts by menu_order before making them draggable
If the administration list is intended to display the persistent manual sequence, its query must retrieve posts in that order.
A custom query might use:
$query = new WP_Query(
array(
'post_type' => 'project',
'orderby' => array(
'menu_order' => 'ASC',
'title' => 'ASC',
),
)
);
The secondary title order makes records sharing the same menu_order value deterministic.
Default WordPress admin lists do not automatically use menu_order
Simply storing:
menu_order = 5
does not guarantee that:
edit.php?post_type=project
will suddenly display posts according to that sequence.
The administration query must deliberately use it.
Use pre_get_posts carefully
The official pre_get_posts hook lets developers modify a WP_Query before its SQL is generated.
For example:
function myplugin_project_admin_order(
$query
) {
if ( ! is_admin() ) {
return;
}
if (
'project' !==
$query->get( 'post_type' )
) {
return;
}
if (
$query->get( 'orderby' )
) {
return;
}
$query->set(
'orderby',
array(
'menu_order' => 'ASC',
'title' => 'ASC',
)
);
}
add_action(
'pre_get_posts',
'myplugin_project_admin_order'
);
The explicit orderby check is useful because it avoids overriding a user who has deliberately clicked another sortable column.
Do not force menu_order after the user chooses another sorting mode
Imagine an editor clicks:
Title ↑
If your plugin still forces:
menu_order ASC
the list-table heading appears interactive while the underlying query ignores the user’s choice.
That is confusing.
A manual-order mode should normally be distinguishable from temporary list-table sorting.
Consider an explicit manual-order mode
For complex administration screens, it can be clearer to expose something such as:
Sort:
[ Manual Order ]
or a dedicated:
Reorder
view.
That avoids ambiguity when the same list table also supports sorting by:
- title;
- date;
- author;
- custom columns.
Only enable dragging when manual order is visible
Suppose the user sorts the table alphabetically:
A
B
C
D
while the persistent order is:
C
A
D
B
If dragging remains enabled in the alphabetical view, what exactly does moving:
B above A
mean?
The interface becomes ambiguous.
A robust implementation should usually allow reordering only when the rows are currently displayed according to the manual sequence.
WordPress already bundles jQuery UI Sortable
WordPress Core registers:
jquery-ui-sortable
as one of its script handles.
The Core wp_default_scripts() implementation registers the Sortable component, so a plugin can enqueue the WordPress-provided dependency rather than bundling another copy.
Enqueue drag-and-drop scripts only on the relevant screen
Do not load ordering JavaScript throughout wp-admin.
Use the administration enqueue hook and inspect the current screen.
The official admin_enqueue_scripts documentation recommends using the page hook or screen information to scope administration assets.
For example:
function myplugin_ordering_assets(
$hook_suffix
) {
if ( 'edit.php' !== $hook_suffix ) {
return;
}
$screen = get_current_screen();
if (
! $screen
||
'project' !== $screen->post_type
) {
return;
}
wp_enqueue_script(
'jquery-ui-sortable'
);
wp_enqueue_script(
'myplugin-project-order',
plugins_url(
'project-order.js',
__FILE__
),
array(
'jquery',
'jquery-ui-sortable',
),
'1.0.0',
true
);
}
add_action(
'admin_enqueue_scripts',
'myplugin_ordering_assets'
);
get_current_screen() helps identify the correct list table
WordPress provides:
get_current_screen()
for inspecting the current administration context.
The official get_current_screen() documentation explains that the returned WP_Screen object exposes properties including the current post type and screen ID.
This is preferable to loading reorder functionality on every administration page.
Add a visible drag handle
Making the entire row draggable can interfere with:
- text selection;
- links;
- checkboxes;
- row actions;
- Quick Edit;
- touch interactions.
A dedicated handle is usually clearer.
For example:
<span
class="myplugin-drag-handle"
aria-label="Reorder item"
>
↕
</span>
The handle visually communicates that the row can be moved.
Accessibility still matters
Drag-and-drop alone is not an ideal interaction for every user.
Consider keyboard users, users with motor impairments and users working through assistive technologies.
The WCAG guidance for dragging movements explains that functionality requiring dragging should have a non-dragging alternative unless dragging is essential.
A production ordering interface can therefore benefit from alternatives such as:
- Move Up;
- Move Down;
- Move to Top;
- Move to Bottom;
- numeric position fields.
Initialize jQuery UI Sortable on the table body
A basic JavaScript implementation might target:
#the-list
which WordPress commonly uses as the body containing administration list-table rows.
For example:
jQuery(function ($) {
$('#the-list').sortable({
items: '> tr',
handle: '.myplugin-drag-handle',
axis: 'y',
update: function () {
saveOrder();
}
});
});
This provides the visual interaction.
It does not yet persist anything.
The new order must be converted into post IDs
WordPress list-table rows commonly include an ID such as:
post-123
After sorting, JavaScript can collect the rows in their new visual sequence.
Conceptually:
post-18
post-42
post-11
post-90
becomes:
18
42
11
90
Example: collect the reordered IDs
function getOrderedPostIds() {
return jQuery('#the-list > tr')
.map(function () {
const id = this.id || '';
if (
! id.startsWith('post-')
) {
return null;
}
return parseInt(
id.replace(
'post-',
''
),
10
);
})
.get()
.filter(Number.isInteger);
}
The resulting array might be:
[18, 42, 11, 90]
Do not trust the browser simply because it is wp-admin
The browser submits the ordering request, so the server must still verify:
- the nonce;
- the user’s capability;
- the post IDs;
- the expected post type;
- the submitted values.
An administration request is not automatically trustworthy because it came from an administration page.
Use an AJAX nonce
The official WordPress Nonce documentation recommends verifying AJAX requests with:
check_ajax_referer()
The function checks whether the submitted nonce matches the intended action.
Nonces are not authorization
This distinction is critical.
A valid nonce establishes request intent within WordPress’s nonce model.
It does not answer:
Is this user allowed
to reorder these posts?
You must separately check capabilities.
Use current_user_can() for authorization
The official current_user_can() documentation provides the standard capability-checking API.
For example, if ordering Projects requires editing Projects, the AJAX callback should verify an appropriate post-type capability.
Depending on the post type architecture, this might involve:
edit_posts
or a custom capability assigned to that post type.
Localize the AJAX URL and nonce
A plugin might expose configuration to its script:
wp_localize_script(
'myplugin-project-order',
'MyPluginOrder',
array(
'ajaxUrl' => admin_url(
'admin-ajax.php'
),
'nonce' => wp_create_nonce(
'myplugin_reorder_projects'
),
)
);
Then JavaScript can submit the sequence to WordPress.
Example AJAX request
function saveOrder() {
const ids =
getOrderedPostIds();
jQuery.post(
MyPluginOrder.ajaxUrl,
{
action:
'myplugin_reorder_projects',
nonce:
MyPluginOrder.nonce,
ids:
ids
}
);
}
For production use, also provide:
- saving feedback;
- success feedback;
- error handling;
- protection against overlapping requests.
Register the authenticated AJAX action
Because post reordering is an administration operation, the callback normally uses:
wp_ajax_{action}
For example:
add_action(
'wp_ajax_myplugin_reorder_projects',
'myplugin_reorder_projects'
);
There is normally no reason to expose this operation through:
wp_ajax_nopriv_...
because logged-out visitors should not be modifying editorial order.
Validate the AJAX request
A basic callback structure can begin with:
function myplugin_reorder_projects() {
check_ajax_referer(
'myplugin_reorder_projects',
'nonce'
);
if (
! current_user_can(
'edit_posts'
)
) {
wp_send_json_error(
array(
'message' =>
'Insufficient permissions.',
),
403
);
}
$ids =
isset( $_POST['ids'] )
? (array) $_POST['ids']
: array();
$ids = array_map(
'absint',
$ids
);
// Continue validation and saving.
}
Validate that every submitted ID belongs to the expected post type
Do not accept an arbitrary list of WordPress post IDs and update all of them.
For every ID, verify:
get_post_type( $post_id )
===
'project'
Otherwise a manipulated request could include IDs from unrelated content types.
Check object-level permissions where appropriate
A user may have general editing access without permission to edit every individual object.
For stricter implementations, check:
current_user_can(
'edit_post',
$post_id
)
for each item that will be changed.
Store the new position in menu_order
WordPress’s official wp_update_post() documentation supports:
menu_order
as an integer field.
A straightforward save loop might be:
$position = 0;
foreach ( $ids as $post_id ) {
if (
'project' !==
get_post_type( $post_id )
) {
continue;
}
if (
! current_user_can(
'edit_post',
$post_id
)
) {
continue;
}
wp_update_post(
array(
'ID' => $post_id,
'menu_order' => $position,
)
);
$position++;
}
Return an explicit response
After the sequence is stored:
wp_send_json_success(
array(
'message' =>
'Order saved.',
)
);
JavaScript can then confirm the save to the user.
Do not leave users guessing whether the order saved
A drag operation visually updates immediately.
That does not mean the server accepted it.
A useful state sequence is:
Dragging
↓
Saving...
↓
Saved
or, on failure:
Saving...
↓
Could not save order
Silent failures are particularly dangerous because the interface appears correct until the next page load restores the old sequence.
Consider reverting the UI when saving fails
If an AJAX request returns an error, you can:
- reload the table;
- restore the previous sequence;
- display an explicit error;
- disable additional dragging until the failure is resolved.
The visual state should not claim an order that WordPress did not successfully persist.
Avoid sending excessive save requests
jQuery UI’s:
update
event can fire after each completed reorder.
That may be acceptable for moderate lists.
For more complex interfaces, consider:
- debouncing;
- a short save delay;
- a manual Save Order button;
- blocking overlapping requests.
A Save Order button can be safer for large changes
Instead of saving after every movement:
drag
→ request
drag
→ request
drag
→ request
you can use:
drag several items
↓
click Save Order
↓
one request
This can reduce server work and make the transaction more intentional.
Pagination creates an important ordering problem
Suppose a post type contains:
500 posts
but wp-admin displays:
20 posts per page
If an editor reorders only page 3, the browser can see only those 20 rows.
It does not know the complete position of all 500 records.
Do not renumber the visible page from zero without context
Imagine page 3 represents positions:
41–60
If your AJAX handler blindly saves:
0–19
those records may suddenly jump ahead of items from pages 1 and 2.
Account for the page offset
If ordering remains paginated, the starting position can be based on:
( current_page - 1 )
×
posts_per_page
For example:
page 3
20 per page
offset =
(3 - 1) × 20
=
40
The first item on page 3 can therefore receive:
menu_order = 40
rather than:
menu_order = 0
But pagination prevents cross-page dragging
An editor cannot directly drag an item from:
page 6
to:
page 1
if the rows are never present in the same interface.
That limitation matters when true arbitrary global ordering is required.
A dedicated reorder screen can solve the pagination problem
For manageable collections, you can create a dedicated interface that loads all orderable records.
For example:
Projects → Reorder
with:
Project A
Project B
Project C
...
Project Z
in one sortable list.
This makes global ordering much easier to understand.
Do not load thousands of records into one drag interface
The opposite extreme creates its own problem.
A reorder page containing:
12,000 posts
is not a reasonable administration interface.
For very large datasets, consider:
- ordering within categories;
- ordering only featured content;
- search-assisted movement;
- numeric rank fields;
- section-based ordering;
- separate collections;
- a more specialized data model.
Filters can make drag-and-drop ambiguous too
Suppose the editor filters:
Category = Branding
and sees:
Project A
Project D
Project H
but the global sequence actually contains:
A
B
C
D
E
F
G
H
If the editor drags:
H
A
D
where should B, C, E, F and G go?
The answer is not obvious.
Disable global reordering inside filtered views unless semantics are clear
One sensible rule is:
Manual ordering available
only when no filters are active.
Alternatively, the application can explicitly support:
order within category
as a separate ordering system.
Do not silently reinterpret a filtered subset as the complete collection.
Search results have the same problem
A search such as:
Search: "design"
may show five items from a 200-item sequence.
Dragging those five rows does not provide enough context to rebuild the complete global order reliably.
Disable reordering during search unless the application has explicit subset-ordering semantics.
Bulk actions and drag handles should coexist cleanly
WordPress list tables include row checkboxes.
Your sortable configuration should not make checking a row initiate a drag.
Using:
handle:
'.myplugin-drag-handle'
rather than making the entire row the drag surface avoids many interaction conflicts.
Quick Edit can also change row markup
WordPress can temporarily insert or transform rows while Quick Edit is active.
A sortable implementation should target only actual post rows and avoid treating Quick Edit interface rows as reorderable content.
Test:
- opening Quick Edit;
- closing Quick Edit;
- saving Quick Edit;
- dragging afterward.
Automatic saves can create concurrent requests
If an editor drags multiple items rapidly, several AJAX requests may overlap.
Imagine:
Request A
18,42,11,90
Request B
42,18,11,90
If Request A finishes after Request B, the older state might overwrite the newer state.
Serialize or debounce reorder requests
Possible strategies include:
- disable dragging while saving;
- queue requests;
- debounce changes;
- save only the latest sequence;
- use an explicit Save Order button.
The correct approach depends on the expected editing workflow.
Concurrent editors are another edge case
Imagine two administrators open the ordering screen simultaneously.
Administrator A saves:
A
B
C
D
Administrator B, still looking at an older state, saves:
D
C
B
A
The second save normally wins unless the application implements conflict detection.
Most sites can accept last-write-wins
For simple curated collections, sophisticated concurrency control may be unnecessary.
But if ordering is business-critical, consider:
- timestamps;
- version identifiers;
- locking;
- explicit save confirmation;
- audit logging.
Reordering posts does not change their publication dates
If you update only:
menu_order
the editorial sequence changes.
You are not conceptually trying to manipulate:
post_date;- publication history;
- author;
- slug;
- taxonomy terms.
Use the field that actually represents order instead of modifying timestamps to fake a sequence.
Do not reorder posts by changing publication dates
A common workaround is:
change dates
until items appear
in the desired order
That damages the semantic meaning of:
publication date
and can affect:
- archives;
- feeds;
- SEO-visible dates;
- sorting elsewhere;
- editorial history.
Use a dedicated ordering field instead.
Drag-and-drop does not automatically affect the front end
After you save:
menu_order
WordPress front-end queries still decide independently how posts should be ordered.
A normal query may continue using:
date DESC
until it explicitly requests:
menu_order ASC
This separation is deliberate and useful.
Example frontend query
$projects = new WP_Query(
array(
'post_type' => 'project',
'posts_per_page' => -1,
'orderby' => array(
'menu_order' => 'ASC',
'title' => 'ASC',
),
)
);
Now the public collection follows the sequence created through drag-and-drop.
Do not apply custom order to every frontend query blindly
A Project post type might appear in:
- the main portfolio;
- search;
- a latest-projects widget;
- taxonomy archives;
- related-content sections.
Only some of those contexts may need the persistent editorial order.
See Should Your Custom Post Order Affect the Front End? for the full decision framework.
Use pre_get_posts narrowly on the front end
If the main custom post type archive should follow the manual sequence:
function myplugin_project_frontend_order(
$query
) {
if ( is_admin() ) {
return;
}
if ( ! $query->is_main_query() ) {
return;
}
if (
! $query->is_post_type_archive(
'project'
)
) {
return;
}
$query->set(
'orderby',
array(
'menu_order' => 'ASC',
'title' => 'ASC',
)
);
}
add_action(
'pre_get_posts',
'myplugin_project_frontend_order'
);
This targets the intended archive instead of rewriting every Project query on the site.
New posts need an ordering strategy
Remember that a newly created post commonly starts with:
menu_order = 0
If existing content uses:
1
2
3
4
5
and the frontend sorts ascending, the new item may unexpectedly appear first.
Decide where new items belong
Possible policies include:
- new items appear first;
- new items automatically appear last;
- new items remain unordered until manually positioned;
- the editor is prompted to position new content.
For curated collections, appending new posts to the end is often predictable.
Automatically append new items where appropriate
A plugin can determine the current maximum order for the relevant post type and assign a new value when a post is first created.
However, this logic needs careful guards to avoid running during:
- autosaves;
- revisions;
- updates to existing posts;
- imports;
- unsupported post types.
Do not use revisions as orderable records
WordPress revisions are also stored in the posts table.
A broad database query that ignores:
post_type
or:
post_status
can accidentally involve records that should never appear in the ordering interface.
Always scope the collection explicitly.
Status filtering also matters
Decide whether the ordering system includes:
- published posts;
- drafts;
- pending posts;
- private posts;
- scheduled posts.
If only published posts participate, a draft that later becomes published needs a defined initial position.
Ordering drafts can be useful for editorial planning
In other workflows, editors may deliberately want to position draft items before publication.
Then the reorder screen might include:
publish
draft
pending
while excluding:
trash
auto-draft
revision
The correct policy depends on what the order represents.
Hierarchical post types need extra thought
A hierarchical post type may contain:
Parent A
├── Child A1
└── Child A2
Parent B
├── Child B1
└── Child B2
A single flat drag list can destroy the distinction between:
hierarchy
and:
sibling order
if the implementation is not designed carefully.
Parent-child hierarchy and menu_order are separate values
WordPress stores parent relationships through:
post_parent
and ordering through:
menu_order
Moving:
Child A2
above:
Child A1
does not inherently need to change its parent.
A hierarchical reorder interface should make that distinction explicit.
Use a tree interface if dragging can change hierarchy
If the intended interaction allows:
drag item under another item
then you are no longer implementing simple sorting.
You are also changing:
post_parent
That requires:
- nested drag-and-drop;
- parent validation;
- cycle protection;
- permission checks;
- separate persistence logic.
Taxonomy term ordering is a different problem
Posts and taxonomy terms are different WordPress object types.
A drag-and-drop system built around:
wp_posts.menu_order
does not automatically provide ordering for:
- categories;
- tags;
- custom taxonomy terms.
Do not mix the two data models.
Cache layers may delay visible frontend changes
After an editor changes the order successfully, the public site may still display the previous sequence because of:
- page caching;
- object caching;
- CDN caching;
- static generation;
- headless application caching.
The ordering system may therefore need to trigger or integrate with cache invalidation.
Headless sites need explicit ordering too
If WordPress is only the CMS and another application renders the frontend, saving menu_order changes the WordPress data.
The frontend still needs to:
- request the data in that order;
- sort it appropriately;
- invalidate cached pages where necessary.
Dragging in wp-admin and seeing the row move does not automatically rebuild an external frontend.
Use TheOneWP Custom Sort Order for a managed workflow
TheOneWP Custom Sort Order provides manually managed ordering for selected WordPress post types.
The module can be used when editors need a persistent custom sequence without building and maintaining the entire drag-and-drop system manually.
The stored ordering can also be configured independently from whether it should affect frontend queries.
This means the workflow can support:
Admin manual order
→ enabled
Front-end manual order
→ disabled
or:
Admin manual order
→ enabled
Front-end manual order
→ enabled
depending on what the content model requires.
Why that separation matters
An editor may want Projects grouped conveniently in wp-admin while the public site continues to display:
newest first
or:
filtered by taxonomy
or:
ranked by another business rule
The visual administration order and public presentation should therefore remain independently configurable.
Drag-and-drop ordering security checklist
- Require authentication.
- Generate a nonce for the reorder action.
- Verify the nonce server-side.
- Check an appropriate capability.
- Check object-level edit permissions when appropriate.
- Convert submitted IDs to integers.
- Reject unsupported post types.
- Ignore revisions and invalid IDs.
- Do not trust submitted order values blindly.
- Return structured success and error responses.
Drag-and-drop UX checklist
- Use a visible drag handle.
- Do not make checkboxes or links accidental drag targets.
- Show saving feedback.
- Show successful save feedback.
- Show failures clearly.
- Consider reverting the interface after failed saves.
- Provide a non-dragging alternative where practical.
- Disable reordering when the current list order is ambiguous.
- Test Quick Edit.
- Test bulk selection.
- Test touch devices.
- Test narrower wp-admin layouts.
Data-model checklist
- Define exactly what the custom order means.
- Choose
menu_orderwhen one persistent sequence is sufficient. - Do not fake ordering with publication dates.
- Choose deterministic secondary ordering.
- Define how new posts receive a position.
- Define how drafts participate.
- Keep hierarchy separate from sibling order.
- Use additional fields when multiple independent orderings are required.
Query checklist
- Order the admin list by
menu_orderwhen drag-and-drop is active. - Do not override explicit list-table sorting unnecessarily.
- Do not enable global dragging inside ambiguous filtered views.
- Handle pagination offsets correctly.
- Apply frontend ordering only to intended queries.
- Do not override search relevance accidentally.
- Do not override latest-content queries accidentally.
- Test archive pagination.
- Test REST and headless consumers where applicable.
Performance checklist
- Load sortable JavaScript only on supported screens.
- Avoid saving after every mouse movement.
- Prevent overlapping requests.
- Do not load thousands of rows into one reorder interface.
- Keep AJAX payloads limited to necessary IDs.
- Use normal WordPress APIs for post updates where practical.
- Review cache invalidation when frontend order changes.
Common drag-and-drop ordering mistakes
- Making rows draggable without saving the result.
- Saving without a nonce.
- Using a nonce without checking capabilities.
- Accepting arbitrary post IDs from the browser.
- Updating unrelated post types.
- Renumbering page 3 from zero.
- Allowing global reordering in filtered search results without defining the semantics.
- Leaving dragging enabled while the table is sorted by Title or Date.
- Changing publication dates instead of using an order field.
- Assuming
menu_orderautomatically changes frontend queries. - Applying custom order to every frontend query.
- Ignoring new posts that start with order zero.
- Confusing hierarchical parent changes with ordinary row ordering.
- Providing no feedback when the save fails.
A complete implementation flow
A robust WordPress drag-and-drop post ordering system can be summarized as:
1. Select supported post types
2. Display them in manual order
3. Add a clear drag handle
4. Load Sortable only on supported screens
5. Collect ordered post IDs
6. Send a nonce-protected request
7. Check user capabilities
8. Validate every post ID
9. Save menu_order values
10. Return success or failure
11. Apply order to intended queries
12. Test filters, pagination and new posts
Related guides
- Should Your Custom Post Order Affect the Front End?
- WordPress List-Table Sorting, Explained
- WordPress Admin List Tables, Explained
- WordPress Post Types vs. Custom Post Types
- WordPress Screen Options, Explained
- How to Safely Run SQL Queries in WordPress
Final recommendation
Drag-and-drop post ordering works best when it is treated as a persistent content-management workflow rather than merely a JavaScript effect attached to a WordPress table.
The underlying model should remain simple:
Editor moves item
↓
browser sends ordered IDs
↓
server verifies request
↓
WordPress saves menu_order
↓
relevant queries use that order
Use WordPress’s existing menu_order field when one persistent sequence accurately represents the content. Use the Core-provided jquery-ui-sortable dependency or another carefully integrated sortable interface for the interaction, and protect every save with both nonce verification and capability checks.
Keep drag-and-drop disabled when filters, searches, pagination or alternative list-table sorting make the meaning of a movement ambiguous. For large collections, consider a dedicated reorder screen or a more specialized ordering model instead of pretending that thousands of records belong in one enormous draggable list.
Most importantly, decide separately whether the saved order belongs only to wp-admin or should also control public queries.
TheOneWP Custom Sort Order provides that managed workflow for selected post types while keeping frontend application of the custom sequence optional.
The result should be predictable for editors: move an item, save a clear persistent sequence and know exactly which parts of the site that sequence is intended to control.

