Bulk Create Posts Using REST API
Not so far time ago I published a similar tutorial for WooCommerce, actually I wasn’t sure that WordPress REST API itself accepts batch requests as well, because of lack of its documentation. But now I have figured it out and would like to share it with you.
In this tutorial we are going to create a bulk action like this one for posts and pages:

Actually if you don’t want to deal with the code, you can check my Simple WP Crossposting plugin which allows to do exactly the same.
Now let’s get started.
First things first we need to display our custom option in bulk actions. It can be done easily with both bulk_actions-edit-{post type}
and handle_bulk_actions-edit-{post type}
hooks. Because we are about to do it for two post types – post
and page
, there are going to be 4 hooks in total:
// for Posts
add_filter( 'bulk_actions-edit-post', 'rudr_bulk_option' );
add_filter( 'handle_bulk_actions-edit-post', 'rudr_do_bulk_action', 10, 3 );
// for Pages
add_filter( 'bulk_actions-edit-page', 'rudr_bulk_option' );
add_filter( 'handle_bulk_actions-edit-page', 'rudr_do_bulk_action', 10, 3 );
If you have plans about using it for more post types, please consider using a loop like this:
$post_types = array( 'post', 'page', 'misha_custom_post_type' );
foreach( $post_types as $post_type ) {
add_filter( 'bulk_actions-edit-' . $post_type, 'rudr_bulk_option' );
add_filter( 'handle_bulk_actions-edit-' . $post_type, 'rudr_do_bulk_action', 10, 3 );
}
We have two callback functions so far:
rudr_bulk_option()
– adds a custom bulk action into a list of bulk actions,rudr_do_bulk_action()
– processes it (to bulk publish posts to the other sites in other words).
Let’s begin with the first callback function which is the simplest one:
function rudr_bulk_option( $bulk_actions ) {
$bulk_actions[ 'publish_to_other_site' ] = 'Publish to the other site';
return $bulk_actions;
}
Yes, it is super simple, even after using this code we already have something:

Of course if you select this new bulk action and try to execute it, nothing will happen. And actually the main part of the code is below:
function rudr_do_bulk_action( $redirect, $doaction, $object_ids ) {
// exit the function if it is not our custom bulk action
if( 'publish_to_other_site' !== $doaction ) {
return $redirect;
}
// we are going to use it to get a post type name
$screen = get_current_screen();
// all the batch requests are going to be in this array
$requests = array();
foreach( $object_ids as $object_id ) {
$post = get_post( $object_id );
// add a part to our batch request
$requests[] = array(
'method' => 'POST',
'path' => ( 'page' === $screen->post_type ? '/wp/v2/pages' : '/wp/v2/posts' ),
'body' => array(
'status' => $post->post_status,
'title' => $post->post_title,
'type' => $post->post_type,
'content' => $post->post_content,
'excerpt' => $post->post_excerpt,
),
);
}
$request = wp_remote_post(
'https://your-site-url/wp-json/batch/v1/',
array(
'headers' => array(
'Authorization' => 'Basic ' . base64_encode( "{$login}:{$pwd}" )
),
'body' => array(
'requests' => $requests
)
)
);
if( 207 === wp_remote_retrieve_response_code( $request ) ) {
$redirect = add_query_arg(
array(
'sync_done' => count( $object_ids ),
),
$redirect
);
}
return $redirect;
}
Let me talk a little bit more about this piece of code
- On lin
9
we’re usingget_current_screen()
function, it is just the way I chose to get the post type (because we’re using this callback function for bothpost
andpage
post types at the same and we need to use different REST API enpoints depending on a post type). Actually if you take a look at line21
you will understand what I am talking about. - On lines
23-27
I decided to provide some post/page data, you can also provide custom fields, featured images, terms etc. - When everything went well the batch REST API request will return 207 status code, we’re checking it on line
45
. If some posts haven’t been published for some reason it doesn’t affect the batch API request, but you can check for errors inside the body of the request. - And the last but not the least, if you don’t know where to get
$login
and$password
, please read about application passwords.
And of course let’s don’t forget about admin notices:
add_action( 'admin_notices', 'rudr_notices' );
function rudr_notices() {
// do nothing if we're on other admin pages
$screen = get_current_screen();
if( 'edit' !== $screen->base ) {
return;
}
if( ! empty( $_REQUEST[ 'sync_done' ] ) ) {
printf( '<div class="updated notice is-dismissible"><p>' . _n( '%s item has been successfully published.', '%s items have been successfully published.', absint( $_REQUEST[ 'sync_done' ] ) ) . '</p></div>', absint( $_REQUEST[ 'sync_done' ] ) );
}
}
Final result:

Updating posts or pages which have already been published
This part actually can be tricky.
In order to apply the bulk action to the posts that have already been published and to avoid duplicates at the same time, we somehow need to connect posts between Site 1 and Site 2. In my opinion it can be implemented two ways – you can either send 1 more API request to search the selected posts by slugs or you can just store an ID of a post copy in custom fields. I prefer the second way for sure because it is faster and allows to have different slugs for posts.
A small hint how to implement it:
if( 207 === wp_remote_retrieve_response_code( $request ) ) {
$body = json_decode( wp_remote_retrieve_body( $request ), true );
if( $body[ 'responses' ] ) {
for( $i = 0; $i < count( $body[ 'responses' ] ); $i++ ) {
// do something to connect posts, for example this:
$id = isset( $body[ 'responses' ][ $i ][ 'body' ][ 'id' ] ) ? absint( $body[ 'responses' ][ $i ][ 'body' ][ 'id' ] ) : 0;
update_post_meta( $object_ids[ $i ], 'post_copy_id', $id );
}
}
...
Oh, and don’t forget that we should change a REST API endpoint when we are updating a post.
Or you can just use my plugin for that 🙂
Maximum requests within a single batch
By default WordPress REST API allows to use up to 25 requests within a single batch, but guess what, you can change it with the help of a filter hook rest_get_max_batch_size
.
For example like this:
add_filter( 'rest_get_max_batch_size', 'rudr_max_batch_size' );
function rudr_max_batch_size() {
return 50;
}
Of course I recommend you to be careful with this hook.

Misha Rudrastyh
Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.
Need some developer help? Contact me
I have three sites A, B and C
– User case 1: How can I stand at site C and post to both of A and B?
– User case 2: How can I stand at site C and choose which site can be post (A or B)?
Thank much you
You just need to select the needed sites from the dropdown