Skip to main content

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 typeType IDWhat it carries
Text Block76474f2f00894e77a0410b39fb17d0bfA Markdown content text value (render as markdown)
Data Blockb8803a8665de412bbb357e0c84adf473Filter (JSON), Sort (JSON), View (table/list/gallery), Data Source (query vs collection)
Imageba4e41460010499da0a3caaa7f579d0eAn IPFS URL text value
Videod7a4817c9795405b93e212df759c43f8An IPFS URL
PDF14a39e59d9874596956ac2dd4165c210An 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

  • position ordering is fractional-index, not numeric. Always sort by string comparison (localeCompare), never Number(position). See Position.generateBetween in the SDK for inserting between two positions.
  • toEntity { name } can be null — Text Blocks often have no Name, only Markdown content. Don't assume a name exists.
  • Block type IDs come from the SDKSystemIds.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 EntityFilter object, then re-issue via entities.
  • 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.