Understanding the JSON output

Magicube outputs content as a plain JSON object. Whether you're writing a simple paragraph or adding different block types like code, images, or titles, everything ends up as structured, readable JSON. This format is the foundation of how content is stored, transported, and rendered.
This structure is designed to be easy to work with. You can save it in a database, send it through an API, or render it wherever you like. Understanding this structure is essential if you plan to build your own custom blocks tailored to your business logic or domain-specific needs.
In this section, we’ll walk through how this format is structured so you can understand how blocks are defined and how the system handles content internally.
Document Structure
The document is the top structure in the JSON output three. A document is represented as an object with two main parts:
  • id: a unique identifier for the document
  • blocks: an ordered list of block objects
{
	"id": "4c1c5b32-7b94-483b-a4c8-dba19fe9c17f",
	"blocks": [
 		// ...
    ]
}
Block Structure
Each block is a JSON object with:
  • a unique id
  • a type field that defines what kind of block it is
  • a content array containing rich text segments (with optional marks like bold, italic, etc.)
  • an optional children array (used by nested structures)
  • a data field for block-specific metadata
Example output for:
Hello World
{
  "id": "d3619bfa-ac90-4283-9e4a-962db17a1cd8",
  "type": "paragraph",
  "content": [
    {
      "text": "Hello World"
    }
  ],
  "children": [],
  "data": {}
}
It's about the marks you leave behind
The content of the block is the text within plus marks. If we add bold mark to the "world", we you have this text visual representation
Hello World
And the block content will be
{
  "id": "d3619bfa-ac90-4283-9e4a-962db17a1cd8",
  "type": "paragraph",
  "content": [
    {
      "text": "Hello "
    },
    {
      "text": "World"
      "bold": true
    }
  ],
  "children": [],
  "data": {}
}
Magicube splits content into text segments that wrap their own marks. If you apply bold to "Hello " as well, it will automatically merge with "world" into a single marked segment. Whether a block supports rich text or not depends on its implementation. For example, paragraphs support rich text by default, while heading blocks from the typography plugin use plain text only.
Wanna have children?
Blocks can also define a children array. This field holds any nested blocks that belong to a parent block. Just like with rich text, whether a block supports children depends on its implementation. For example, paragraphs accept children by default, while image blocks do not. Below is an example of a block that contains nested content, along with its JSON representation.
Hello
World
{
  "id": "cc43e481-47cc-4377-8264-18b21a731d73",
  "type": "paragraph",
  "content": [
    {
      "text": "Hello"
    }
  ],
  "children": [
    {
      "id": "7e650b8b-4983-4c29-84fd-92d48e7324e1",
      "type": "paragraph",
      "content": [
        {
          "text": "World"
        }
      ],
      "children": [],
      "data": {}
    }
  ],
  "data": {}
}
Big data
Last but not least, there's the data field. This is where a block stores custom information relevant to its behavior or configuration. It’s fully flexible and can be shaped to fit the block’s needs. For example, the toggle-list block uses data to track whether it's open or closed.
While you could manage this with a local useState in React, storing it in data means the state is persisted inside the document itself. If a toggle is open when saved, it will still be open when rendered. No extra logic required.
{
  "type": "toggle-list",
  "data": {
    "isOpen": true
  },
  "content": [
    {
      "text": "TOGGLE EXAMPLE. This is shown whether the toggle is open or closed."
    }
  ],
  "children": [
    {
      "type": "paragraph", 
      "content": [
        {
          "text": "It won't be shown if the toggle is closed (isOpen is false)"
        }
      ]
    }
  ]
}
Now that we’ve understood the foundations of the block structure, we’re finally ready to build our own custom blocks. In the next section, we’ll learn how to create a custom Callout Block, a visually distinct block perfect for tips, alerts, or highlights. It’s a great first example because it combines rich text formatting with custom data, showing how to go beyond the basics while keeping things intuitive.