Portable Text bullet list structure: single object vs separate objects?
The behavior you're seeing is actually correct according to the Portable Text specification. Each list item is stored as a separate block object, not as children of a single list container.
According to the Portable Text specification, there is no actual "list" node type in Portable Text. Instead, lists are represented as a series of individual list item blocks that share the same level and listItem properties. The rendering libraries (like @portabletext/react or @portabletext/vue) handle grouping these consecutive list item blocks into visual lists when displaying them.
Here's what the JSON structure looks like for a bullet list:
[
{
"_type": "block",
"style": "normal",
"listItem": "bullet",
"level": 1,
"children": [
{ "_type": "span", "text": "First item" }
]
},
{
"_type": "block",
"style": "normal",
"listItem": "bullet",
"level": 1,
"children": [
{ "_type": "span", "text": "Second item" }
]
}
]Each list item block has:
listItem: The type of list ("bullet"for bullet lists or"number"for numbered lists)level: The nesting level (1 for top-level, 2 for nested, etc.)
When you render this with the official Portable Text libraries, they automatically group consecutive blocks with the same listItem and level values into a cohesive <ul> or <ol> element.
Why this flat structure? This design makes it easier to:
- Edit individual list items independently in Sanity Studio
- Handle nested lists with different levels
- Mix list items with other block types
- Query and manipulate list content with GROQ
For parsing: If you're building a custom renderer, you'll need to implement the grouping logic yourself—look for consecutive blocks with matching listItem and level properties and wrap them in a list container. However, the official libraries handle this automatically, so I'd recommend using @portabletext/react, @portabletext/vue, or similar for your framework rather than building custom parsing logic from scratch.
Show original thread4 replies
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.