Skip to content
Foliosodev
Menu
On this page

REST API

TipTap JSON reference

A page's body is stored as a TipTap JSON document, the format the editor itself saves. This page describes every node, mark and attribute the editor uses, with an example of each and the Markdown Folioso writes for it.

You get this format from content on a page, a recycle-bin page or a snapshot, and you can send it as content when you create or update a page. To work in Markdown instead, see Content formats.

The document

The body is one doc node. Its content is an array of the page's top-level blocks:

json
{
  "type": "doc",
  "content": [
    { "type": "heading", "attrs": { "level": 2 }, "content": [{ "type": "text", "text": "Install" }] },
    {
      "type": "paragraph",
      "content": [
        { "type": "text", "text": "Run " },
        { "type": "text", "text": "composer install", "marks": [{ "type": "code" }] },
        { "type": "text", "text": " first." }
      ]
    }
  ]
}
markdown
## Install

Run `composer install` first.

A page with no body has content of null, not a document. A document with an empty content array is a page with no blocks.

Nodes

Every node is a JSON object:

Key Present on Description
type Every node The node's type, such as paragraph.
attrs Nodes with attributes An object of attribute values. An attribute left out takes its default, so {"type":"heading","attrs":{"level":2}} has textAlign null.
content Nodes that hold other nodes An array of child nodes. Left out, it's the same as [].
text text nodes The text.
marks text, mention and pageLink nodes An array of marks, such as bold or a link.

Folioso keeps whatever JSON you send in content, without checking it against the rules below. The editor does apply them when it opens the page, so a node it doesn't know, or one in a place it can't go, may be dropped or moved when the page is next edited. Follow the rules on this page, and when in doubt, make the content in the editor and read it back with GET /pages/{slug}.

What can go where

Node Kind Content
doc The document Blocks and alert boxes, at least one. The editor opens a doc with "content": [] as one empty paragraph.
paragraph, heading Block Inline nodes.
blockquote Block One or more blocks.
bulletList, orderedList Block One or more listItem.
listItem Inside a list A paragraph, then any blocks.
taskList Block One or more taskItem.
taskItem Inside a task list A paragraph, then any blocks.
codeBlock Block text nodes without marks.
mermaid Block Nothing. The source is an attribute.
horizontalRule, image Block Nothing.
table Block One or more tableRow.
tableRow Inside a table tableHeader and tableCell nodes.
tableHeader, tableCell Inside a row One or more blocks.
alertBox Top level only An alertTitle, then an alertBody.
alertTitle Inside an alert box Inline nodes.
alertBody Inside an alert box One or more paragraph.
text, hardBreak, mention, pageLink Inline Nothing.

"Blocks" means every block node except alertBox: an alert box can only sit at the top level of the page.

Block nodes

paragraph

A paragraph of text.

Attribute Type Default Description
textAlign string or null null "center" or "right". null is left-aligned.
json
{ "type": "paragraph", "attrs": { "textAlign": "center" }, "content": [{ "type": "text", "text": "Centred text." }] }
markdown
[](folioso:attrs?textAlign=center)Centred text.

An empty paragraph, {"type":"paragraph"}, is an empty line on the page.

heading

A heading.

Attribute Type Default Description
level integer 1 1 to 6.
textAlign string or null null As for paragraph.
json
{ "type": "heading", "attrs": { "level": 3 }, "content": [{ "type": "text", "text": "Rollback" }] }
markdown
### Rollback

blockquote

A quote. It has no attributes and holds blocks.

json
{ "type": "blockquote", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Measure twice." }] }] }
markdown
> Measure twice.

bulletList and listItem

A bulleted list. Neither node has attributes. Each listItem starts with a paragraph and can hold further blocks, including nested lists.

json
{
  "type": "bulletList",
  "content": [
    { "type": "listItem", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Redis" }] }] },
    {
      "type": "listItem",
      "content": [
        { "type": "paragraph", "content": [{ "type": "text", "text": "Postgres" }] },
        {
          "type": "bulletList",
          "content": [{ "type": "listItem", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Version 18" }] }] }]
        }
      ]
    }
  ]
}
markdown
- Redis
- Postgres

  - Version 18

orderedList

A numbered list of listItem nodes.

Attribute Type Default Description
start integer 1 The first item's number.
type string or null null The numbering style, as HTML's type: "a", "A", "i" or "I". null numbers with digits.
json
{
  "type": "orderedList",
  "attrs": { "start": 1 },
  "content": [
    { "type": "listItem", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Build" }] }] },
    { "type": "listItem", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Ship" }] }] }
  ]
}
markdown
1. Build
2. Ship

Markdown has no syntax for type, so a list with one is written as a JSON block.

taskList and taskItem

A checklist. Each taskItem starts with a paragraph and can hold further blocks, including nested task lists.

Attribute On Type Default Description
checked taskItem boolean false Whether the box is ticked.
json
{
  "type": "taskList",
  "content": [
    { "type": "taskItem", "attrs": { "checked": true }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Tests pass" }] }] },
    { "type": "taskItem", "attrs": { "checked": false }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Changelog" }] }] }
  ]
}
markdown
- [x] Tests pass
- [ ] Changelog

codeBlock

A block of code, holding plain text nodes with no marks. Line breaks are \n characters in the text.

Attribute Type Default Description
language string or null Set by each reader The language slug, such as "php" or "bash". null is plain text.

The editor's language menu uses typescript, javascript, php, python, go, rust, bash, sql, html, css, yaml, json and dockerfile, with null for Plain text. Any other string is kept and shown by name. See Code blocks.

language has no fixed default. A block with no language key at all is shown in each reader's own preferred language, which is why the editor always saves language explicitly. Send it explicitly too.

json
{ "type": "codeBlock", "attrs": { "language": "php" }, "content": [{ "type": "text", "text": "echo 'Hello';\necho 'World';" }] }
markdown
```php
echo 'Hello';
echo 'World';
```

An empty code block has no content.

mermaid

A diagram, drawn from its source. The type is named mermaid for both engines.

Attribute Type Default Description
code string "" The diagram's source.
engine string "mermaid" "mermaid" or "plantuml". PlantUML source includes its @startuml and @enduml lines.
json
{ "type": "mermaid", "attrs": { "code": "@startuml\nAlice -> Bob: Hello\n@enduml", "engine": "plantuml" } }
markdown
```plantuml
@startuml
Alice -> Bob: Hello
@enduml
```

See Diagrams.

horizontalRule

A horizontal line. No attributes or content.

json
{ "type": "horizontalRule" }
markdown
---

image

An image, always a block of its own.

Attribute Type Default Description
src string null The image's address.
alt string or null null The alt text, shown as the caption.
title string or null null The image's title.
width integer or null null The width in pixels, set by the resize handles. null is the image's natural width, up to the page width.
height integer or null null The height in pixels. The editor leaves it null and keeps the image's proportions.
textAlign string or null null "center" or "right" places the image.
json
{
  "type": "image",
  "attrs": {
    "src": "https://folioso.dev/storage/images/0b9f4c5e-8d7a-4f1e-9c2b-3a4d5e6f7a8b.webp",
    "alt": "Request flow",
    "title": null,
    "width": 640,
    "height": null,
    "textAlign": null
  }
}
markdown
[](folioso:attrs?width=640)![Request flow](<https://folioso.dev/storage/images/0b9f4c5e-8d7a-4f1e-9c2b-3a4d5e6f7a8b.webp>)

To use an image you upload through the API, put the url the upload returns in src, exactly as returned. A page write that names it keeps the upload. See Images.

An image whose address no longer works shows Image unavailable in the editor.

table, tableRow, tableHeader and tableCell

A table is made of rows, and each row of header and ordinary cells. table and tableRow have no attributes. A new table in the editor has a header row, and Toggle header row and Toggle header column change which cells are tableHeader.

Attribute On Type Default Description
colspan cells integer 1 How many columns the cell spans.
rowspan cells integer 1 How many rows the cell spans.
colwidth cells array or null null The column widths in pixels, one per column the cell spans, such as [240]. null lets the column size itself.
align cells string or null null A cell alignment the editor doesn't set. Align a cell's paragraph with its textAlign instead.

Cells hold blocks, so a cell's text is a paragraph:

json
{
  "type": "table",
  "content": [
    {
      "type": "tableRow",
      "content": [
        { "type": "tableHeader", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Queue" }] }] },
        { "type": "tableHeader", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Latency" }] }] }
      ]
    },
    {
      "type": "tableRow",
      "content": [
        { "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "mail" }] }] },
        { "type": "tableCell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "seconds" }] }] }
      ]
    }
  ]
}
markdown
| Queue | Latency |
| --- | --- |
| mail | seconds |

When cells span, a row has fewer cells. A table with spans, with cells holding more than one paragraph or other blocks, with no header row, or with header cells outside the first row has no pipe-table form, so it's written as a JSON block:

json
{
  "type": "table",
  "content": [
    {
      "type": "tableRow",
      "content": [{ "type": "tableHeader", "attrs": { "colspan": 2 }, "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Merged" }] }] }]
    },
    {
      "type": "tableRow",
      "content": [
        { "type": "tableCell", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "a" }] }] },
        { "type": "tableCell", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "b" }] }] }
      ]
    }
  ]
}
markdown
```folioso
{"type":"table","content":[{"type":"tableRow","content":[{"type":"tableHeader","attrs":{"colspan":2},"content":[{"type":"paragraph","content":[{"type":"text","text":"Merged"}]}]}]},{"type":"tableRow","content":[{"type":"tableCell","content":[{"type":"paragraph","content":[{"type":"text","text":"a"}]}]},{"type":"tableCell","content":[{"type":"paragraph","content":[{"type":"text","text":"b"}]}]}]}]}
```

alertBox, alertTitle and alertBody

An alert box: a titled, coloured panel. It's made of exactly one alertTitle, a line of inline content that can carry marks, then one alertBody of paragraphs. Neither part has attributes. Alert boxes can only be top-level blocks, and their paragraphs can't be aligned.

Attribute On Type Default Description
type alertBox string "info" "info", "success", "warning" or "error".
json
{
  "type": "alertBox",
  "attrs": { "type": "success" },
  "content": [
    { "type": "alertTitle", "content": [{ "type": "text", "text": "Deployed" }] },
    { "type": "alertBody", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "Version 4.2 is live." }] }] }
  ]
}
markdown
> [!TIP] Deployed
>
> Version 4.2 is live.

An empty title is {"type":"alertTitle"}. The body always holds at least one paragraph, which can be empty.

type Shown as Written in Markdown as
info Info [!NOTE]
success Success [!TIP]
warning Warning [!WARNING]
error Error [!CAUTION]

Inline nodes

Inline nodes sit inside paragraphs, headings and alert titles.

text

A run of text. text must be a non-empty string: the editor refuses a document holding an empty text node. Neighbouring text with the same marks can be one node or several; both are the same.

json
{ "type": "text", "text": "Hello" }

hardBreak

A line break within a block. No attributes.

json
{
  "type": "paragraph",
  "content": [{ "type": "text", "text": "Line one" }, { "type": "hardBreak" }, { "type": "text", "text": "Line two" }]
}
markdown
Line one\
Line two

mention

A mention of a person, shown as @ and their name. Saving a page with a new mention notifies the person, following the rules in Page links and mentions.

Attribute Type Default Description
id number or string null The person's user id. The editor stores it as a number.
label string or null null The name shown.
mentionSuggestionChar string "@" The character the mention is shown with.
taggedBy number or null null The user id of the person who added the mention. The editor sets it.
json
{
  "type": "paragraph",
  "content": [
    { "type": "text", "text": "Thanks " },
    { "type": "mention", "attrs": { "id": 5, "label": "Ada Lovelace", "mentionSuggestionChar": "@", "taggedBy": 7 } },
    { "type": "text", "text": "!" }
  ]
}
markdown
Thanks [@Ada Lovelace](folioso:mention?id=5&taggedBy=7)\!

Mentions aren't included in search.

A link to another Folioso page, shown with the target page's current title. Pages that link to a page are listed in its Linked from.

Attribute Type Default Description
id number null The target page's numeric id.
slug string null The target page's slug, which the editor uses to open it.
json
{
  "type": "paragraph",
  "content": [
    { "type": "text", "text": "See " },
    { "type": "pageLink", "attrs": { "id": 42, "slug": "9b2f6c1e-4d1a-4b8e-9f0a-2c3d4e5f6a7b" } },
    { "type": "text", "text": "." }
  ]
}
markdown
See [Deploy runbook](folioso:page?id=42&slug=9b2f6c1e-4d1a-4b8e-9f0a-2c3d4e5f6a7b).
  • The title isn't stored. Each reader sees the title of a page they can open, and Restricted page for one they can't, so a page link never reveals a title to someone who can't see it. The Markdown label works the same way.
  • Links to a page that doesn't exist are kept in the content but don't count in Linked from.
  • Page links aren't included in search.

Marks

Marks style the text they're on. A node's marks array lists each mark once, and Folioso keeps them in this order: link, bold, code, italic, strike, underline.

json
{
  "type": "paragraph",
  "content": [
    { "type": "text", "text": "bold", "marks": [{ "type": "bold" }] },
    { "type": "text", "text": " " },
    { "type": "text", "text": "italic", "marks": [{ "type": "italic" }] },
    { "type": "text", "text": " " },
    { "type": "text", "text": "struck", "marks": [{ "type": "strike" }] },
    { "type": "text", "text": " " },
    { "type": "text", "text": "underlined", "marks": [{ "type": "underline" }] },
    { "type": "text", "text": " " },
    { "type": "text", "text": "code", "marks": [{ "type": "code" }] }
  ]
}
markdown
**bold** *italic* ~~struck~~ [underlined](folioso:span?marks=underline) `code`
Mark Attributes Description
bold None Bold.
italic None Italic.
strike None Strikethrough.
underline None Underline.
code None Inline code. It can't be combined with any other mark.
link See below A link to an address.

Marks combine, so [{"type":"bold"},{"type":"italic"}] is bold italic, written ***both*** in Markdown.

A link on a run of text.

Attribute Type Default Description
href string null The address.
target string or null "_blank" Where the link opens. The editor always uses "_blank", a new tab.
rel string or null "noopener noreferrer" The link relationship. The editor always uses "noopener noreferrer".
class string or null null Not used by the editor.
title string or null null The link's title.
json
{
  "type": "paragraph",
  "content": [
    { "type": "text", "text": "Read the " },
    {
      "type": "text",
      "text": "guide",
      "marks": [
        { "type": "link", "attrs": { "href": "https://example.com/guide", "target": "_blank", "rel": "noopener noreferrer", "class": null, "title": null } },
        { "type": "bold" }
      ]
    },
    { "type": "text", "text": "." }
  ]
}
markdown
Read the [**guide**](<https://example.com/guide>).

href is stored as given in JSON, whatever its scheme. A link with target, rel or class other than the defaults has no Markdown form and is written as a JSON block. To link to another Folioso page, use a pageLink rather than a link to its address.