Page + Blocks of a Space
Spaces have a "front door" — the page entity — which holds the space's name, description, and a stack of blocks that render the homepage layout (markdown sections, data tables, image galleries, etc.). This recipe walks page → blocks → block contents.
The shape
Space
└── page (Entity, the space's homepage)
├── values (Name, Description, Cover image, etc.)
└── relations
└── (Blocks) → Block entities, ordered by position
├── Text Block (markdown content)
├── Data Block (Query) (live query → table/list/gallery view)
├── Data Block (Collection) (hand-picked entities)
└── Image / Video / PDF
Query — page + ordered blocks
query SpaceHomepage($spaceId: UUID!) {
space(id: $spaceId) {
page {
id
name
relations(
first: 50
filter: { typeEntity: { name: { is: "Blocks" } } }
) {
totalCount
nodes {
id
position
toEntity {
id
name
types { id name }
values(first: 10) {
nodes {
propertyEntity { name }
text
}
}
}
}
}
}
}
}
{ "spaceId": "41e851610e13a19441c4d980f2f2ce6b" }
Response — AI space homepage (2 blocks)
{
"data": {
"space": {
"page": {
"id": "8cb0a2b4adbf4627aa080cec5112099a",
"name": "AI",
"relations": {
"totalCount": 2,
"nodes": [
{
"id": "334bbddab6724e599138522c80e50f8e",
"position": "a21Ow",
"toEntity": {
"id": "10c20b954d8b4fa9881d7b949341219d",
"name": "Latest news stories",
"types": [{ "id": "b8803a8665de412bbb357e0c84adf473", "name": "Data block" }],
"values": {
"nodes": [
{ "propertyEntity": { "name": "Name" }, "text": "Latest news stories" },
{ "propertyEntity": { "name": "Sort" }, "text": "{\"sort_by\":\"...\",\"sort_direction\":\"descending\"}" },
{ "propertyEntity": { "name": "Filter" }, "text": "{\"spaceId\":{\"in\":[\"41e85...\"]},\"filter\":{\"8f15...\":{\"is\":\"e550...\"}}}" }
]
}
}
},
{ "id": "f6e1be...", "position": "a1", "toEntity": { "name": "Topics", "types": [{ "name": "Data block" }] } }
]
}
}
}
}
}
Loading interactive query runner…
How to render
The position field is a fractional-index string — use a string-natural sort to get block order:
const blocks = data.space.page.relations.nodes
.sort((a, b) => a.position.localeCompare(b.position))
.map(r => r.toEntity);
Then dispatch on the block's type:
| Block type | Type ID | What it carries |
|---|---|---|
| Text Block | 76474f2f00894e77a0410b39fb17d0bf | A Markdown content text value (render as markdown) |
| Data Block | b8803a8665de412bbb357e0c84adf473 | Filter (JSON), Sort (JSON), View (table/list/gallery), Data Source (query vs collection) |
| Image | ba4e41460010499da0a3caaa7f579d0e | An IPFS URL text value |
| Video | d7a4817c9795405b93e212df759c43f8 | An IPFS URL |
14a39e59d9874596956ac2dd4165c210 | An IPFS URL |
Render a Text Block
// Find the markdown value on a Text Block entity
const markdownValue = block.values.nodes.find(
(v) => v.propertyEntity?.name === "Markdown content"
);
const md = markdownValue?.text ?? "";
// Render md with your markdown library of choice
Render a Data Block (Query)
A Query Data Block holds a JSON Filter value that you parse and re-issue against the entities query:
const filterValue = block.values.nodes.find(
(v) => v.propertyEntity?.name === "Filter"
);
const filter = JSON.parse(filterValue.text);
const data = await gql(
`query($filter: EntityFilter!) { entities(filter: $filter, first: 50) { id name } }`,
{ filter }
);
// Now render `data.entities` according to the block's View
Notes
positionordering is fractional-index, not numeric. Always sort by string comparison (localeCompare), neverNumber(position). SeePosition.generateBetweenin the SDK for inserting between two positions.toEntity { name }can benull— Text Blocks often have no Name, only Markdown content. Don't assume a name exists.- Block type IDs come from the SDK —
SystemIds.TEXT_BLOCK,SystemIds.DATA_BLOCK,SystemIds.IMAGE_TYPE,SystemIds.VIDEO_TYPE. Don't hardcode. - Data Block filters are stringified JSON, not GraphQL syntax. Parse them, build a real
EntityFilterobject, then re-issue viaentities. - Page itself is a regular entity — has its own values (Name, Description, Cover) and its own relations (Blocks, Tabs). Treat it like any other entity for inspection.