Lists & relations
Most pages are, at heart, lists: a guide curates places, a place shows its reviews, an index lists everything of a type. CTXR has exactly three ways to build a list, and the difference between them isn’t cosmetic — it decides where the list lives, when it’s computed, and what happens when a member is unpublished.
Three kinds of list
curated hand-picked, hand-ordered stored on the parent
relational derived from who points back computed from the topology index
queried matched on properties/type computed fresh from the indexes
Curated — an editor explicitly includes specific nodes and arranges them. A guide’s mainEntity.itemListElement is a curated list of stops; order is set by each item’s position. The list is the parent’s data, resolved into the parent’s compiled page. (See curatorial vs relational for how this inlining works.)
Relational — assembled from the reverse direction. “All reviews about this place” isn’t stored on the place; the reviews point to the place, and the list is the reverse lookup, answered on demand and cached.
Queried — assembled by matching. “All places in this space” is a type query; “every node with this OUP identifier” is an identifier query. Nothing is stored — the list is computed from the indexes at render time.
Unpublish behaviour differs
This is the practical reason the distinction matters:
| List type | Remove a member by… | Effect |
|---|---|---|
| Curated | editing the parent | The parent recompiles without it |
| Relational | unpublishing the member | The topology index updates automatically; lists everywhere drop it |
| Queried | unpublishing the member | It simply stops matching — no edit anywhere needed |
Unpublishing one popular place quietly removes it from every relational and queried list it appeared in, without anyone touching those pages. Curated lists are the only ones that need an explicit edit — which is exactly right, because a curated list is an editorial decision, not a derived fact.
Derived values
When the compiler resolves a list, it also measures it. Values you’d otherwise compute by hand are derived and kept accurate automatically:
{
"@type": "TouristTrip",
"name": "Stadswandeling",
"_stopCount": 7, // counted across all stops
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.6", "reviewCount": 128 // computed across all reviews
}
}
stopCount, reviewCount, aggregateRating and friends are calculated over the full set of related nodes — even the ones the compiler didn’t inline. A place with a thousand reviews shows an accurate reviewCount of 1000 while its live record carries only the first handful inline. The number is always true; the payload stays bounded.
Projection
A list rarely needs every field of every member. Projection is the compiler taking only the properties a given context needs. A map sidebar projects places down to name and geo; a review strip projects them to name and image. The same node appears in several lists, each carrying a different slice of it.
Projection is what keeps compiled documents small without you writing per-list field selectors. (The API exposes the same idea explicitly — the fields parameter on a query projects results down to the keys you ask for.)
How the system tracks it all
Behind every relational and queried list is the topology index — a derived map of which node references which, and whether each link is curatorial or relational. It powers the reverse lookups and tells the compiler which parents to recompile when a member changes. It’s rebuildable from the data itself: delete it and the next compile reconstructs it exactly. You never maintain it — you just rely on the three list types behaving the way this page describes.