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
- bulk_actions-edit-post filter registers the new action. The key mark_as_featured is what will be passed to the handler.
- handle_bulk_actions-edit-post receives three parameters: the redirect URL (string), the action key (string), and an array of selected post IDs. You must return the redirect URL.
- Perform per-post capability checks with current_user_can(edit_post, post_id) to avoid escalating privileges.
- Use add_query_arg to add a query variable that the admin_notices action can read to display a message showing how many posts were processed.
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(
,
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(
,
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
- Capability checks: Always verify current_user_can for the appropriate capability (e.g., edit_posts or edit_post per post) before changing content.
- Nonce: The bulk actions form already includes a nonce for bulk-posts, but if you rely on custom POST inputs (for example, if you add a category dropdown) validate those with check_admin_referer or your own nonce field.
- Sanitize inputs: Cast post IDs with intval(), sanitize strings with sanitize_text_field(), and validate term IDs with absint() or term_exists().
- Per-item checks: Check edit_post capability for each post to avoid editing posts the user is not allowed to edit.
Performance and scaling
- For a small number of posts the simple foreach approach is fine. For thousands of posts you should batch operations in chunks, or process asynchronously (background processing) to avoid timeouts. Use WP Cron, Action Scheduler, or a background process library.
- If updating taxonomy terms for many posts, consider temporarily suspending term counting or using bulk term assignment functions carefully to reduce overhead.
- Avoid expensive operations inside the loop where possible (for example unnecessary get_post() calls or expensive meta queries).
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
- Install the plugin on a development site, not on production.
- Use various roles (administrator, editor, author) to confirm capability checks work properly.
- 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.
- Test with other plugins and custom code to ensure you dont conflict with existing bulk actions using the same action key.
- Check error logs and WP_DEBUG to find warnings or errors.
Common pitfalls and solutions
- Action not showing in the dropdown: Make sure you used the correct filter name: bulk_actions-edit-{post_type}. Clear object cache and check user capability.
- Handler never called: Ensure you registered handle_bulk_actions-{screen} with the appropriate screen and that your action key exactly matches the key added to the bulk_actions filter.
- No feedback to the user: Return a redirect URL with add_query_arg and show a notice using admin_notices. The handle_bulk_actions filter requires returning the redirect URL.
- Security issues: Remember to check per-post capabilities and sanitize inputs.
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
- Did you use the right filter name for the specific post-type screen?
- Is your plugin active and are caches cleared?
- Are there any JavaScript errors in the browser console preventing the form submit?
- Are you sure the user role has the required capability?
- Did you return the redirect URL in the handle_bulk_actions callback?
Further reading and references
Summary
To create custom bulk actions in WordPress posts list:
- Use bulk_actions-{screen} to add a new action to the dropdown.
- 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.
- Use admin_notices to display the outcome to the user using values passed in the redirect URL.
- 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 🙂
|
¡Si te ha servido el artículo ayúdame compartiendolo en algún sitio! Pero si no te ha sido útil o tienes dudas déjame un comentario! 🙂
Related