WP Manifestindependent plugin directory
manifest / content / wp-tag-order

WP Tag Order WP Registry grade D+. High-severity findings · 3 findingsWP RegistryHigh-severity findings · 3 findingsOpen the reportD+

↕︎ Order tags independently in each posts (not site-globally) on WordPress.

★ 21stars
414composer installs
v3.16.2latest · 3mo ago

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/sectsect/wp-tag-order/archive/refs/heads/master.zip

On Packagist as sectsect/wp-tag-order:

composer require sectsect/wp-tag-order
Updates itself. It ships its own updater, so new versions appear under Dashboard → Updates.

Order tags independently in each posts (not site-globally) on WordPress with simple Drag-and-Drop ↕︎ sortable feature.

[!IMPORTANT] This plugin is NOT compatible with Gutenberg on WordPress 5.x. Consider using Classic Editor Plugin.

[!IMPORTANT] Mutation events will no longer be supported in Google Chrome and Edge in July 2024 (Google Chrome on July 23, 2024, and Microsoft Edge the week of July 25, 2024). As a result, WP Tag Order versions 3.6.0 or less will not work with Chrome v127 and later, which will be released on July 23, 2024.
You have to update to v3.7.0 or later.
See Chrome for Developers Blog for more details.
If you still need PHP7 support, see Troubleshooting below.

Features

  • 🏷️ Custom tag ordering for posts
  • 🔍 Works with default WordPress tag and custom taxonomy systems
  • 🔢 Drag-and-Drop interface for easy tag reordering
  • 📊 Supports multiple post types and taxonomies
  • 🔧 NEW: Programmatic Tag Order Management
  • 🌐 NEW: REST API Support
    • Retrieve ordered tags via GET endpoint
    • Update tag order programmatically
    • Supports authentication and permissions
  • 🚀 Lightweight and performance-optimized

Get Started

  1. Clone this Repo into your wp-content/plugins directory.
    cd /path-to-your/wp-content/plugins
    git clone git@github.com:sectsect/wp-tag-order.git
  2. Activate the plugin through the Plugins menu in WordPress.
  3. Go to Settings -> WP Tag Order page to select which taxonomies to enable ordering for.

Notes

  • When creating a new post, you need to save it once to enable tag ordering.
  • To apply ordering to existing posts, "Add and Remove" any tag once.
  • To bulk-update multiple posts at once, go to Settings -> WP Tag Order page and click 'Apply' under the Advanced Settings.
  • Tested on WordPress v6.3.1.

Requirements

  • WordPress 5.6+
  • PHP 8.0+

APIs

Function Description
get_the_tags_ordered() Based on get_the_tags() - Codex
get_the_terms_ordered() Based on get_the_terms() - Codex
get_the_tag_list_ordered() Based on get_the_tag_list() - Codex
get_the_term_list_ordered() Based on get_the_term_list() - Codex
the_tags_ordered() Based on the_tags() - Codex
the_terms_ordered() Based on the_terms() - Codex

Usage Example

get_the_tags_ordered()

<?php
$terms = get_the_tags_ordered();
if ( $terms && ! is_wp_error( $terms ) ) :
?>
<ul>
    <?php foreach ( $terms as $term ) : ?>
        <li>
            <a href="<?php echo get_term_link( $term->slug, 'post_tag' ); ?>">
                <?php echo $term->name; ?>
            </a>
        </li>
    <?php endforeach; ?>
</ul>
<?php endif; ?>

get_the_terms_ordered()

<?php
$terms = get_the_terms_ordered( $post->ID, 'post_tag' );
if ( $terms && ! is_wp_error( $terms ) ) :
?>
<ul>
    <?php foreach ( $terms as $term ) : ?>
        <li>
            <a href="<?php echo get_term_link( $term->slug, 'post_tag' ); ?>">
                <?php echo $term->name; ?>
            </a>
        </li>
    <?php endforeach; ?>
</ul>
<?php endif; ?>

get_the_tag_list_ordered()

<?php echo get_the_tag_list_ordered(); ?>

get_the_term_list_ordered()

<?php echo get_the_term_list_ordered( $post->ID, 'post_tag' ); ?>

the_tags_ordered()

<?php the_tags_ordered(); ?>

the_terms_ordered()

<?php the_terms_ordered( $post->ID, 'post_tag' ); ?>

Programmatic Tag Order Management

The plugin provides a Tag_Updater class for developers to programmatically manage tag order:

$tag_updater = new \WP_Tag_Order\Tag_Updater();

try {
    // Update tag order using an array or comma-separated string
    $result = $tag_updater->update_tag_order(
        get_the_ID(),   // Post ID
        'post_tag',     // Taxonomy that enabled in plugin settings
        [1, 2, 3]       // Tag IDs in desired order
    );
} catch ( \InvalidArgumentException $e ) {
    // Error handling
    error_log( $e->getMessage() );
}

This class allows flexible tag order updates directly from your theme or custom plugin code, supporting both array and string inputs with robust validation.

Return Value

update_tag_order() returns: int|bool

  • int: Meta ID if the tag order meta key didn't previously exist
  • true: Successful update of existing meta
  • false: Update failed or the new tag order is identical to the existing order

REST API

The WP Tag Order plugin provides two REST API endpoints for managing tag order:

Get Tag Order

  • Endpoint: /wp-json/wp-tag-order/v1/tags/order/{post_id}
  • Method: GET
  • Parameters:
    • post_id (required): The ID of the post
    • taxonomy (optional): Taxonomy name (defaults to 'post_tag')
  • Permissions: Publicly accessible
  • Response: Array of ordered tags with full term details

Example Request

GET /wp-json/wp-tag-order/v1/tags/order/123
w/ cURL
curl --location 'https://your-wordpress-site.com/wp-json/wp-tag-order/v1/tags/order/123'

Update Tag Order

  • Endpoint: /wp-json/wp-tag-order/v1/tags/order/{post_id}
  • Method: PUT or PATCH
  • Parameters:
    • post_id (required): The ID of the post
    • taxonomy (required): Taxonomy name
    • tags (required): Comma-separated list of tag IDs in desired order
  • Permissions: Requires user authentication and post edit capabilities
  • Response: Success status and message

Example Request

PUT /wp-json/wp-tag-order/v1/tags/order/123
{
  "taxonomy": "post_tag",
  "tags": "5,3,1,4,2"
}
w/ cURL
curl --location --request PUT 'https://your-wordpress-site.com/wp-json/wp-tag-order/v1/tags/order/123' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_TOKEN' \
--data '{
  "taxonomy": "post_tag",
  "tags": "5,3,1,4,2"
}'

Example Response

{
  "success": true,
  "code": "tags_order_updated",
  "message": "Tag order updated successfully.",
  "data": {
    "status": 200,
    "post_id": 123,
    "taxonomy": "post_tag",
    "tags": [
      5,
      3,
      1,
      4,
      2
    ]
  }
}

Get Enabled Taxonomies

  • Endpoint: /wp-json/wp-tag-order/v1/taxonomies/enabled
  • Method: GET
  • Parameters: None
  • Permissions: Publicly accessible
  • Response: Array of enabled taxonomies with metadata

Example Request

GET /wp-json/wp-tag-order/v1/taxonomies/enabled
w/ cURL
curl --location 'https://your-wordpress-site.com/wp-json/wp-tag-order/v1/taxonomies/enabled'

Example Response

{
  "enabled_taxonomies": [
    "post_tag",
    "product_tag"
  ],
  "available_taxonomies": [
    "post_tag",
    "product_tag",
    "event_tag"
  ],
  "meta": {
    "enabled_count": 2,
    "available_count": 3,
    "timestamp": "2025-09-23T04:58:23+00:00"
  }
}

Authentication

  • GET requests are publicly accessible
  • PUT/PATCH requests require:
    • User to be logged in
    • User to have edit permissions for the specific post
    • Post must be of an allowed type (default: 'post' or 'page')

For Developers

  • The ordered tag data is serialized and stored in the wp_postmeta table under keys like wp-tag-order-{taxonomy}.

    meta_id post_id meta_key meta_value
    19 7 wp-tag-order-post_tag s:91:"a:7:{i:0;s:1:"7";i:1;s:1:"5";i:2;s:2:"10";i:3;s:1:"4";i:4;s:1:"6";i:5;s:1:"8";i:6;s:1:"9";}";
  • This Plugin does not hosting on the wordpress.org repo in order to prevent a flood of support requests from wide audience. Your feedback is welcome.

Troubleshooting

Still need support for PHP 7.

I have a branch php7 to support PHP 7 and End-of-Life for JaveScript Mutation events.
The branch will not be maintained anymore, so I recommend you migrate to PHP 8.

Change log

See CHANGELOG file.

License

See LICENSE file.

✌️

A little project by @sectsect

Releases

Newest 25 of 60 recorded releases.

Tag
Published
v3.16.2 latest
Jul 12, 2026 3mo ago
Apr 28, 2026 5mo ago
Feb 16, 2026 8mo ago
Feb 13, 2026 8mo ago
Nov 17, 2025 11mo ago
Nov 17, 2025 11mo ago
Nov 16, 2025 11mo ago
Nov 16, 2025 11mo ago
Sep 23, 2025 1 year ago
Aug 27, 2025 1 year ago
Aug 8, 2025 1 year ago
Jul 13, 2025 1 year ago
Jun 4, 2025 1 year ago
Feb 5, 2025 1 year ago
Feb 5, 2025 1 year ago
Feb 2, 2025 1 year ago
Jan 6, 2025 1 year ago
Jan 4, 2025 1 year ago
Dec 24, 2024 1 year ago
Dec 22, 2024 1 year ago
Dec 21, 2024 1 year ago
Dec 17, 2024 1 year ago
Nov 27, 2024 1 year ago
Nov 27, 2024 1 year ago
Nov 26, 2024 1 year ago
All releases on GitHub →

These releases are tags only. The author does not attach a packaged zip, so there are no download counts to report.

D+ grade

Security

WP Tag Order 3.16.2 · audited by WP Registry

High-severity findings.

1 high 2 medium
Audited release
3.16.2
Findings
3
Worst severity
high
Content hash
720363aa872d175ae20318a5…

Findings

  • high Contributor+ can assign or create terms on any post via nonce-only AJAX

    missing_capability

    ajax_wto_sync_tags is registered on both wp_ajax_wto_sync_tags and wp_ajax_nopriv_wto_sync_tags. After a POST-method check it only verifies wp_verify_nonce/check_ajax_referer against the posted action string (wto_sync_tags). There is no current_user_can('edit_post'), no post-type allowlist, and no wp_tag_order_is_enabled_taxonomy / is_taxonomy_hierarchical check. load_wpto_admin_script localizes that nonce on post.php/post-new.php for anyone who can open the editor (Contributor+ when at least one taxonomy is enabled). WordPress nonces are per-user/per-action, not per-post, so the Contributor can replay nonce_sync against a different id. The handler explodes posted tag names, calls wp_set_object_terms($post_id, $newtag, $taxonomy, true) which inserts missing terms, then wp_set_object_terms($post_id, $ids, $taxonomy) replacing the whole set, plus update_post_meta(wp-tag-order-{taxonomy}). taxonomy is only FILTER_SANITIZE_FULL_SPECIAL_CHARS, so category (or any CPT taxonomy such as product_cat) works — a capability Contributors do not have via the UI. Empty tags deletes that meta. ajax_wto_update_tags is the same gate and writes order meta for any post through Tag_Updater (that path at least requires an enabled non-hierarchical taxonomy). save_wpto_meta_box correctly requires edit_post; these AJAX paths do not. Subscriber/unauth never see the nonce on a default role map, so this is not a customer-account unauth RCE, but it is object-level authorization failure for every role that can edit any post. wto_options is the same nonce-only/nopriv pattern but its nonce is only printed on the manage_options settings screen.

    includes/index.php:342-499

    Recommendation

    Remove the nopriv hooks. In both AJAX handlers require is_user_logged_in() and current_user_can('edit_post', $post_id) (same as save_wpto_meta_box). Bind the nonce to the post (e.g. wto_sync_tags-{$post_id}). Allowlist taxonomy with wp_tag_order_validate_taxonomy() before wp_set_object_terms. Do not create terms unless the user can assign/manage that taxonomy.

  • medium Unauthenticated REST GET discloses tags for private and draft posts

    information_disclosure

    register_rest_route GET /wp-tag-order/v1/tags/order/(?P<post_id>\d+) uses wp_tag_order_rest_permission_check, which returns true for every GET before any post or capability test. wp_tag_order_get_post_tag_order then get_post_meta($post_id, wp-tag-order-{taxonomy}) (taxonomy query param defaults to post_tag and is not validated against enabled/public taxonomies) and returns term_id, name, slug, description, count, etc. Core REST hides draft/private/trash posts from anonymous users; this route does not check post_status, read_post, or whether the post exists as a public object. An unauthenticated client can enumerate post IDs and recover unpublished tag names and term descriptions. The sibling GET /taxonomies/enabled uses a permission_callback that always returns true; that payload is only taxonomy slugs and is not this issue.

    includes/rest-api.php:186-264

    Recommendation

    For GET, require current_user_can('read_post', $post_id) (or at least that the post is publicly viewable). Validate taxonomy with wp_tag_order_validate_taxonomy(). Do not return term details for posts the requester cannot read via core REST.

  • medium REST tag update allows post authors who cannot edit_post to replace terms

    missing_capability

    For non-GET methods, wp_tag_order_rest_permission_check returns true if current_user_can('edit_post', $post_id) OR get_current_user_id() === (int) $post->post_author. WordPress Contributors lose edit_post on their posts once an Editor publishes them (no edit_published_posts). The OR lets that Contributor still authenticate to POST/PUT/PATCH /wp-tag-order/v1/tags/order/{post_id} with a normal logged-in REST nonce (available in wp-admin). wp_tag_order_update_post_tag_order then Tag_Updater::update_tag_order and wp_set_object_terms($post_id, $tags, $taxonomy), replacing the post's terms — not merely storing order meta. Tags must already exist and the taxonomy must pass wp_tag_order_validate_taxonomy (enabled, non-hierarchical, public or show_in_rest), so this is narrower than the AJAX sync handler, but it is still a published-content write the role is denied in core. Unauthenticated callers are blocked by is_user_logged_in(); Subscribers who did not author the post are blocked.

    includes/rest-api.php:186-219

    Recommendation

    Drop the post_author shortcut and require current_user_can('edit_post', $post_id) alone (and assign_terms on that taxonomy). Do not call wp_set_object_terms from an order-update endpoint unless the user can edit the post; persist order meta only.

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.