If you’re building a custom block that renders a list of posts, you’ll need a way to get those posts! I’ll cover the pros and cons of each so you can choose the right approach for your situation when it comes up.
When you’re working in the block editor (which uses React and JavaScript) you have a different set of tools available to you than you do in PHP templating. As with many web dev subjects, there are numerous ways you could get a list of posts, terms, users, etc.
Most of the time, you may find you’re using the same approach, but in the Real World™️ there are always edge cases that may require you to color outside the lines a bit. So to start, let’s talk about a few scenarios where you might need to do this kind of thing in the first place.
Real world examples of dynamic post fetching
If your only goal was to just display a list of posts for someone to browse and maybe include pagination, you may find the core Query Loop block meets your needs. In general, if you can reasonably use an existing block, you probably should!
That said, when you’re using the built-in blocks, you don’t have as much control over the markup and output of those blocks. And sometimes, that’s ok! However, the core block(s) you’d use to do this only have so many options and sometimes it’s easier to just make your own block than to try and tack on extra features or capabilities to the core blocks.
So let’s talk through what some of those situations look like.
Specific design or markup requirements
You may find yourself on a project where a designer(s) have made high-fidelity mockups for the various blocks you need to create. Those blocks might have design qualities that change based on the width of the block or the layout might change entirely at a very specific breakpoint (ex: 540px).
WordPress itself uses two main breakpoints: 360px (Mobile) and 768px (Tablet). So it breaks down like this:
| Screen Width | Breakpoint |
|---|---|
| 0-360px | Mobile |
| 361px-768px | Tablet |
| > 769px | Desktop |
WordPress itself doesn’t provide a way to set styles based on breakpoint yet. Additionally, you don’t have full control over the markup of blocks to add any HTML element or attribute you want.
There are third party block plugins (such as GenerateBlocks) that let you do more advanced things with markup and queries, but using those isn’t always an option especially when you’re doing work on larger teams or at the enterprise level. On many such projects the tool/plugin choices may have been already made before you started on the project.
So if you really need to control the output of the block and do styling things that go beyond the available Inspector Controls, you may find you need to build a custom block to accomplish that.
Using post data outside of a post loop context
The Query Loop is intended to display a list of posts that someone can click on (think like a blogroll, related posts, etc). If you’re needing to get a list of posts and then use that information to do something other than just display a list of posts for someone to click, you’ll want to use your own block.
A simple example of this is the core Calendar block. This block displays a calendar that shows which posts were published on a given day and month. This block isn’t just looping over the posts and making a list of them, it’s showing them in a calendar. So it needs to look up those blocks directly.
Displaying Meta data
Core blocks don’t have an easy way to display any post meta data you may want in a loop of posts. You could use Block Bindings to “bind” the value of that post meta to the text content of a paragraph block or some other block/attribute combination, but that functionality is somewhat limited.
For instance, Block Bindings don’t support partial replacement. Let’s say you had a block value called message and you wanted to do something like have “The message is: <MESSAGE_HERE>”. That wouldn’t be possible using Block Bindings because the meta value of the message would be found to the entire text content of the block instead of just one part of it.
Also, if the meta value in question is a non-scalar value like an array or object, you wouldn’t be able to use it as a block binding anyway.
With that context established, let’s move on to some code examples!
Our Custom Block: post-list
For the purposes of this post I’m going to use a hypothetical post-list block to illustrate the different techniques you may use to get posts. This is a dynamic block that I scaffolded using the excellent create-block package and I removed a few comments for better clarity.
Here’s an example of how this block would render in the editor using all of the examples below:

Using the REST API
If you’re used to getting posts in PHP using WP_Query or getting dynamic data in JavaScript apps, your first instinct might be to just use the REST API to fetch posts. WordPress has a robust REST API that has been around for some time now (longer than the block editor) and they have a handy apiFetch function you can use to easily make calls to it.
The apiFetch function is effectively a wrapper for fetch that has a few helpful things done internally to make it easy to call the WordPress REST API with it.
// edit.js
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { useEffect, useState } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import './editor.scss';
export default function Edit() {
const [ posts, setPosts ] = useState( null );
// Use effect to fetch posts on mount.
useEffect( () => {
// Setup async fetch function to get posts and set the state.
async function fetchPosts() {
try {
const posts = await apiFetch( {
path: '/wp/v2/posts',
method: 'GET',
} );
// Update state with the posts we fetched.
setPosts( posts );
} catch ( error ) {
console.error( error );
setPosts( [] );
}
}
fetchPosts();
}, [] ); // Empty array to only run on mount.
return (
<div { ...useBlockProps() }>
{ ! Array.isArray( posts ) ? (
<div>Loading...</div>
) : (
posts?.map( ( { id, title } ) => {
return <div key={ id }>{ title.rendered }</div>;
} )
) }
</div>
);
}
In the above example, we are fetching a list of posts and then storing the fetched posts in state. We then render the post state in a simple list below.
This option will give you the most control over the API request itself. You can do a lot of advanced things here like specify a specific root URL or security nonce. However, if you’re just getting a list of posts and aren’t doing more advanced things like that, it might be overkill for your needs.
Using getEntityRecords
Now let’s take a look at using native WordPress data modules to do these requests. The concept of these data modules is inspired by Redux (a state management library for React) and revolves around using a “store” of data to get or set information.
A store is a group of data that is loaded in memory via a resolver. You can then get data from the store using a selector function or update data in the store using an action function that will dispatch changes to the store.
So in our case, if we’re wanting to get a list of posts, we’d use a selector function such as getEntityRecords, which will allow us to retrieve data from the core-data store. If you check out that link you can see the various actions, selectors, and hooks available inside it (more on those later).
We can ask the core-data store for the actions and selectors we need as well as keep track of the progress of our request using a WordPress hook called useSelect. Let’s take a look at how this might look:
// edit.js
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import './editor.scss';
export default function Edit() {
// Get the posts and check if the data has resolved.
const { posts, hasResolved } = useSelect( ( select ) => {
return {
posts: select( coreStore ).getEntityRecords( 'postType', 'post' ),
hasResolved: select( coreStore ).hasFinishedResolution(
'getEntityRecords',
[ 'postType', 'post' ]
),
};
}, [] ); // Empty deps array in this case.
// If the data has not resolved, show a loading message.
return (
<div { ...useBlockProps() }>
{ ! hasResolved ? (
<div>Loading...</div>
) : (
posts?.map( ( { id, title } ) => {
return <div key={ id }>{ title.rendered }</div>;
} )
) }
</div>
);
}In the above example, we import the useSelect hook and the coreStore string value from core-data which allows us to say where we want to get our data from. We then get the posts and have a check if our request completed or not.
In our useSelect, we don’t specify any dependencies (deps) because there aren’t any. We’re not adjusting our query in response to one of the block’s attributes or things like that which would change how we’re looking up the posts. But if we were, those dynamic values should be included in the dependency array.
There’s a few key differences between this and the previous example:
- useSelect will create a subscription to the stores you call with the
selectfunction inside of it. - getEntityRecords will cache the results for you automatically (which the REST API does not do)
This example is more declarative in the sense that we’re just saying which type of posts we want and don’t have to specify an endpoint or handle exceptions/errors the same way. Using a pattern like this, you don’t have to worry about things like using an abort controller to stop the fetch if the block component isn’t “mounted” in the browser and things like that.
Now let’s see an even more straightforward option!
useEntityRecords
This is a custom hook that implements getEntityRecords under the hood and makes it even easier to request a list of posts like we are here. The hook uses the same arguments as getEntityRecords that you can easily pass in and get a list of posts with even less code than the previous example:
// edit.js
import { __ } from '@wordpress/i18n';
import { useBlockProps } from '@wordpress/block-editor';
import { useEntityRecords } from '@wordpress/core-data';
import './editor.scss';
export default function Edit() {
// Get the posts and check if the data has resolved.
const { records: posts, hasResolved } = useEntityRecords(
'postType',
'post'
);
// If the data has not resolved, show a loading message.
return (
<div { ...useBlockProps() }>
{ ! hasResolved ? (
<div>Loading...</div>
) : (
posts?.map( ( { id, title } ) => {
return <div key={ id }>{ title.rendered }</div>;
} )
) }
</div>
);
}In this example, we call useEntityRecords which returns an object that includes a few keys:
recordshasResolvedhasStartedisResolvingstatustotalItemstotalPages
I used destructuring to easily pluck out the 2 keys I wanted and name the records key posts so it was the same as the previous examples. This is a common pattern in modern React and JavaScript but you could also just assign them as keys like this:
// Get the posts and check if the data has resolved.
const request = useEntityRecords(
'postType',
'post'
);
const posts = request.records;
const hasResolved = request.hasResolved;Conclusion
You now know the 3 main ways you’ll be looking up posts in the block/site editor! Once you have the posts you can then display them or their data in whatever way you need.
There is a lot of additional information out there about the concepts I’ve covered in this post and I’ve included some handy links you’ll want to check out below. Happy learning!