The WordPress REST API at /wp-json/wp/v2/posts returns a generous response by default — 30+ fields including raw and rendered content, GUID, status, meta, embedded author, embedded featured media, and full links objects. For a front-end application that only needs the post title, excerpt, author name, and featured image URL, this default response transfers many kilobytes of unused data per request. The API provides three built-in mechanisms to reduce this overhead. The _fields query parameter restricts the response to specific field names, returning only what you request. The _embed parameter triggers server-side embedding of linked resources — author, featured media, terms — eliminating additional HTTP requests to fetch them separately. Pagination is communicated through response headers (X-WP-Total and X-WP-TotalPages) rather than the response body, so clients know exactly how many more pages to fetch without an additional count query.
Problem: Your JavaScript front-end queries the REST API for a post list and needs only the title, excerpt, featured image URL, and author name — but the default response includes 30+ fields and requires separate requests for the featured image and author details.
Solution: Use ?_fields=id,title,excerpt,author,featured_media&_embed=true to restrict fields and embed related resources. Read X-WP-Total and X-WP-TotalPages headers for pagination state.
/**
* Fetch paginated post list from WordPress REST API.
* Returns { posts: [], total: number, totalPages: number }.
*/
async function fetchPosts( page = 1, perPage = 10 ) {
const params = new URLSearchParams( {
_fields: 'id,title,excerpt,date,author,featured_media,_links,_embedded',
_embed: 'true', // embeds author and wp:featuredmedia in one request
per_page: perPage,
page: page,
status: 'publish',
} );
const response = await fetch( `/wp-json/wp/v2/posts?${params}` );
if ( ! response.ok ) {
throw new Error( `API error ${response.status}` );
}
const posts = await response.json();
const total = parseInt( response.headers.get( 'X-WP-Total' ), 10 );
const totalPages = parseInt( response.headers.get( 'X-WP-TotalPages' ), 10 );
// Embedded data is under _embedded after using _embed=true
const formatted = posts.map( post => ( {
id: post.id,
title: post.title.rendered,
excerpt: post.excerpt.rendered,
date: post.date,
authorName: post._embedded?.author?.[0]?.name ?? '',
featuredImg: post._embedded?.['wp:featuredmedia']?.[0]?.source_url ?? null,
} ) );
return { posts: formatted, total, totalPages };
}
// Fetch all pages:
async function fetchAllPosts() {
const { posts: firstPage, totalPages } = await fetchPosts( 1 );
const remaining = await Promise.all(
Array.from( { length: totalPages - 1 }, ( _, i ) => fetchPosts( i + 2 ) )
);
return [ ...firstPage, ...remaining.flatMap( r => r.posts ) ];
}
NOTE: The _fields parameter only restricts the top-level response fields. To access _embedded data (author, featured media) you must include both _links and _embedded in _fields, and add _embed=true. Without _links in the field list, the embedded resources won't be included even with _embed=true. Also, the REST API nonce for authenticated requests is available via wp_localize_script as wpApiSettings.nonce — include it as the X-WP-Nonce header when making write requests or accessing protected endpoints.