WordPress REST API Batch Requests: Send Multiple API Calls in One HTTP Request with /batch/v1

WordPress 5.6 introduced a batch processing endpoint at /wp/v2/batch/v1 that allows multiple REST API requests to be sent in a single HTTP call. Without batching, a JavaScript app that needs to update five posts, create two new terms, and update site settings has to make eight separate HTTP requests — each with its own TCP connection overhead, authentication header, and round-trip latency. The batch endpoint accepts an array of virtual requests (each with a method, path, and optional body) and returns an array of virtual responses. By default, responses are returned only after all requests complete (validation: require-all-validate) but the endpoint also supports a parallel processing mode. This is a key optimisation for block editor apps, Gutenberg-based admin tools, and headless WordPress front-ends that make many REST calls on page load.

Problem: An admin dashboard widget makes five separate REST requests on load to fetch posts, users, stats, settings, and taxonomy counts. The waterfall of five sequential fetch calls causes a 1.5 s delay before the dashboard renders. The requests need to be collapsed into a single round-trip.

Solution: Use the /wp/v2/batch/v1 endpoint to send all five requests in one POST call. Each request specifies its HTTP method and path. Handle the array of responses in the callback.

// ── Client-side batch request ─────────────────────────────────────────
const batchResponse = await fetch( '/wp-json/wp/v2/batch/v1', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-WP-Nonce':   wpApiSettings.nonce,  // from wp_localize_script
    },
    body: JSON.stringify( {
        validation: 'require-all-validate', // abort all if any fails validation
        requests: [
            {
                method: 'GET',
                path:   '/wp/v2/posts?per_page=5&status=publish',
            },
            {
                method: 'GET',
                path:   '/wp/v2/users?per_page=5',
            },
            {
                method: 'GET',
                path:   '/wp/v2/settings',
            },
            {
                method: 'GET',
                path:   '/wp/v2/categories?per_page=20&hide_empty=true',
            },
            {
                // POST request inside the batch
                method: 'POST',
                path:   '/my-plugin/v1/log-visit',
                body:   { page: 'dashboard', ts: Date.now() },
            },
        ],
    } ),
} );

const { responses } = await batchResponse.json();
// responses is an array matching the requests array, each with:
// { status: 200, headers: {...}, body: { ... } }

const [ postsRes, usersRes, settingsRes, catsRes, logRes ] = responses;

if ( postsRes.status === 200 ) {
    renderPosts( postsRes.body );
}
if ( usersRes.status === 200 ) {
    renderUsers( usersRes.body );
}

<?php
// ── Pass nonce to JavaScript for batch authentication ─────────────────
add_action( 'admin_enqueue_scripts', function () {
    wp_localize_script( 'my-dashboard-script', 'wpApiSettings', [
        'root'  => esc_url_raw( rest_url() ),
        'nonce' => wp_create_nonce( 'wp_rest' ),
    ] );
} );

// ── Batch respects permission_callback on each individual route ───────
// No special server-side setup needed — the batch endpoint calls
// each route's permission_callback as if it were a direct request.
// Your custom routes are automatically batch-compatible.

// ── Limit batch size if needed (e.g. prevent abuse on public endpoints)
add_filter( 'rest_batch_max_requests', function (): int {
    return 10; // maximum requests per batch call (default: 25)
} );

NOTE: The batch endpoint uses validation: 'require-all-validate' by default, which means all requests are validated (permission checks and argument validation run for all) before any are executed — if any fails validation, none execute and a 207 Multi-Status response is returned with the error codes. Use 'normal' validation to execute requests independently even if some fail. The batch endpoint cannot include requests to external APIs or to WordPress admin-ajax — only to registered REST routes. It also does not reduce database query count: each batched request runs its own queries, so the benefit is purely network round-trip reduction.