A listing block fetches content from the server (e.g. latest news) and renders each result as a separate block. expandListingBlocks(layout, options) walks a layout, fetches results for each listing-type block, and returns { items, paging } where items is an array of block objects with @uid and @type.
You tell it which block types need fetching via a fetchItems map — keys are block types, values are fetcher functions. This means you can have different kinds of listings (Plone queries, RSS feeds, etc.) each with their own fetcher:
A grid can have a mix of listing and static blocks sharing a single paging. The staticBlocks helper wraps non-listing blocks so they participate in the shared page window. The listings use Suspense so they load client-side:
ploneFetchItems({ apiUrl, contextPath, extraCriteria }) — creates a fetcher function for Plone backends, suitable as a value in the fetchItems map. Normalizes results by packaging image_field + image_scales into a self-contained image object { @id, image_field, image_scales }.
For non-Plone backends (RSS feeds, external APIs, etc.), write your own fetcher: async (block, { start, size }) => { items, total }.
fieldMapping on a listing block controls which fields appear on expanded items — only mapped fields are included. Default: { @id → href, title → title, description → description, image → image }. Values can be a string (rename) or { field, type } for conversions:
Use itemType (or variation) on the listing block to control what @type expanded items get. Combined with inheritSchemaFrom, the listing's sidebar shows fields from the selected item type:
If your frontend embeds state in the URL path (like pagination), you need to tell hydra.js how to transform the frontend path to the API/admin path. Otherwise, the admin will try to navigate to URLs that don't exist in the CMS.
The pathToApiPath function is called whenever hydra.js sends a PATH_CHANGE message to the admin, allowing your frontend to strip or transform URL segments that are frontend-specific (like pagination, filters, or other client-side state).
Both expandListingBlocks and staticBlocks return { items, paging }. You pass { start, size } as input (not mutated) and get back computed paging values:
Expanded listing items share the listing block's @uid. Selecting any expanded item selects the parent listing block.