How to create custom bulk actions in the posts list with PHP in WordPress

Contents

Introduction

This tutorial explains, in comprehensive detail, how to create custom bulk actions in the WordPress posts list using PHP. It covers the required hooks, secure handling, feedback to the user, examples for common tasks (update status, assign a taxonomy term, set post meta), JavaScript confirmation, performance considerations, and troubleshooting tips. Code examples are included and placed inside the required

 ... 

(or the appropriate language) blocks.

What is a bulk action in WordPress?

Bulk actions allow administrators and editors to select multiple posts (or other post types) in the admin list table and apply a single action to all selected items at once. WordPress provides core actions like Move to Trash, Edit, and Change status. You can add your own custom actions to extend the admin workflow.

Key hooks and functions youll use

  • bulk_actions-{screen} (filter) — Adds custom entries to the bulk actions dropdown for a particular screen (for posts screen the screen ID is typically edit-post).
  • handle_bulk_actions-{screen} (filter) — Handles the POST when a specific bulk action is triggered. It receives the redirect URL, the action, and an array of selected post IDs it must return the redirect URL.
  • get_current_screen() — Helpful for determining the admin list screen.
  • current_user_can() — Check capabilities before acting on posts.
  • check_admin_referer() — Nonce checks where appropriate.
  • wp_redirect() and add_query_arg() — Redirect back with status messages.
  • admin_notices (action) — Show notices in the admin area after redirect.

Supported screens and screen IDs

The filter name includes the screen ID. For posts, use bulk_actions-edit-post. For pages, use bulk_actions-edit-page. For a custom post type named book, use bulk_actions-edit-book. The handle filter uses the same pattern: handle_bulk_actions-edit-post (replace post with your post type).

Basic plugin structure example

Below is a minimal plugin file that registers a custom bulk action called Mark as Featured on the posts list. The action will update a post meta value for each selected post. The code demonstrates the recommended practice for adding the action, handling it, performing capability checks, updating data safely, and returning a redirect with feedback.

%s

, / translators: %d: number of posts processed / sprintf( _n( %d post marked as featured., %d posts marked as featured., count, custom-bulk-actions ), count ) ) } } add_action( admin_notices, cba_admin_notices )

Explanation of the example

More advanced examples

Example 1 — Bulk assign a category (taxonomy term)

This example demonstrates assigning a category to selected posts. It assumes a built-in taxonomy category. For custom taxonomies adjust the taxonomy name and term handling accordingly.

 ids ) )
        if ( ! in_array( term_id, current_terms, true ) ) {
            new_terms = array_merge( current_terms, array( term_id ) )
            if ( wp_set_post_terms( post_id, new_terms, category ) ) {
                count  
            }
        }
    }

    redirect_to = add_query_arg( bac_assigned, count, redirect_to )
    return redirect_to
}
add_filter( handle_bulk_actions-edit-post, bac_handle_bulk_actions, 10, 3 )

function bac_admin_notice() {
    if ( ! empty( _REQUEST[bac_assigned] ) ) {
        count = intval( _REQUEST[bac_assigned] )
        printf(
            

%s

, sprintf( _n( %d post assigned a category., %d posts assigned a category., count, bulk-assign-category ), count ) ) } } add_action( admin_notices, bac_admin_notice )

Example 2 — Bulk update post status with undo-like feedback

This example updates the post status to private for selected posts and returns a count. If you need undo functionality similar to core Trash action, youd normally rely on cores undo system or implement a custom transient to track changed post IDs so you can revert them on user request. Below is a simpler version that just updates status and reports counts.

 post_id,
            post_status => private,
        ), true )

        if ( ! is_wp_error( updated ) ) {
            count  
        }
    }

    redirect_to = add_query_arg( bmp_count, count, redirect_to )
    return redirect_to
}, 10, 3 )

add_action( admin_notices, function() {
    if ( ! empty( _REQUEST[bmp_count] ) ) {
        count = intval( _REQUEST[bmp_count] )
        printf(
            

%s

, sprintf( _n( %d post set to private., %d posts set to private., count, bulk-make-private ), count ) ) } } )

Adding a confirmation dialog with JavaScript

Its often a good idea to add a confirmation dialog when an action is destructive or significant. You can enqueue an admin script that watches the bulk action selects and intercept the form submit. Below is a small JavaScript snippet. Note: the snippet is shown as a raw block in a plugin youd enqueue it properly with wp_enqueue_script and pass localized strings with wp_localize_script.

jQuery( document ).ready( function(  ) {
    // Bulk actions have two select elements: top and bottom. Hook into the submit of the post listing form.
    ( #posts-filter ).on( submit, function( e ) {
        var action = ( select[name=action], this ).val()
        var action2 = ( select[name=action2], this ).val()
        var confirmActionKeys = [ mark_as_featured, make_private, assign_category ] // update to your action keys

        // Check top action
        if ( -1 !== confirmActionKeys.indexOf( action ) ) {
            if ( ! confirm( Are you sure you want to perform this bulk action? This cannot be undone easily. ) ) {
                e.preventDefault()
                return false
            }
        }

        // Check bottom action
        if ( -1 !== confirmActionKeys.indexOf( action2 ) ) {
            if ( ! confirm( Are you sure you want to perform this bulk action? This cannot be undone easily. ) ) {
                e.preventDefault()
                return false
            }
        }
    } )
} )

How to enqueue the JavaScript properly (PHP snippet)


Security considerations

Performance and scaling

Localization and internationalization

Use translation functions like __(), _e(), _n() and load_plugin_textdomain() so your action labels and admin messages are translatable. In code examples above youll see __() and _n() used.

Testing and debugging

  1. Install the plugin on a development site, not on production.
  2. Use various roles (administrator, editor, author) to confirm capability checks work properly.
  3. Test with zero posts selected, one post selected, and multiple posts selected. Some browsers or JS may behave differently for empty selections WP core normally blocks empty selection and shows a warning.
  4. Test with other plugins and custom code to ensure you dont conflict with existing bulk actions using the same action key.
  5. Check error logs and WP_DEBUG to find warnings or errors.

Common pitfalls and solutions

Using a class-based approach

You can encapsulate the code in a class for cleaner organization, namespace safety, and easier reusability. The code structure is the same you register methods instead of standalone functions with add_filter/add_action.

%s

, sprintf( _n( %d item processed., %d items processed., count, textdomain ), count ) ) } } } new My_Bulk_Actions()

Troubleshooting checklist

Further reading and references

Summary

To create custom bulk actions in WordPress posts list:

  1. Use bulk_actions-{screen} to add a new action to the dropdown.
  2. Use handle_bulk_actions-{screen} to handle the action: iterate over selected post IDs, perform capability checks, execute safe updates, and return a redirect URL containing status info.
  3. Use admin_notices to display the outcome to the user using values passed in the redirect URL.
  4. Optionally add JavaScript for confirmations or richer UI elements (but always enforce security server-side).

Final notes

Follow WordPress coding standards, secure all inputs, and test on a staging environment. For heavy operations, implement batching or background processing to avoid timeouts and poor UX.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *