edit.pink
Pink Editor docs

Blocks

The block vocabulary of an article's body and an answer — prose blocks and typed widgets.

An article's body and a question's answer are arrays of blocks. Every block has a _type. There are two families:

  1. Prose blocks: headings, paragraphs, lists, quotes, images, code.
  2. Typed blocks: widgets with a fixed shape that your site renders with its own components.

A _type you don't know: skip it (and log it, if you like). New block types are added without a new protocol version, so a receiver must never fail on one.

Prose blocks

_typeShape
heading{ "_type": "heading", "level": 1, "text": "…" }, level 1 to 4
paragraph{ "_type": "paragraph", "text": "…" } or with "children": [inline nodes]
bulletList{ "_type": "bulletList", "items": ["…", "…"] }
numberedList{ "_type": "numberedList", "items": ["…", "…"] }
blockquote{ "_type": "blockquote", "text": "…", "cite": "…" }, cite optional
image{ "_type": "image", "url": "https://…", "alt": "…", "caption": "…" }, caption optional
code{ "_type": "code", "language": "ts", "code": "…" }, language optional

Inline nodes

A paragraph's children are inline nodes, also keyed by _type:

_typeShape
text{ "_type": "text", "text": "…" }
link{ "_type": "link", "url": "https://…", "text": "…" }
entityLinkA link to an entity your site defines
sourceCitation{ "_type": "sourceCitation", "source_id": "src-1" }. Render only when the page's sources has that id; see citations.

Internal links arrive already resolved to your page URLs; a link to a page that isn't live yet arrives as plain text.

Treat every string as text. Blocks are written by a model and an editor; render them as text, never as HTML, and only follow http(s), mailto, tel and site-relative URLs.

Typed blocks

statBar

{
  "_type": "statBar",
  "layout": "horizontal",
  "stats": [
    { "value": "3x", "label": "Faster planning" },
    { "value": "50%", "label": "Time saved" }
  ]
}

layout is "horizontal" (default) or "vertical"; 2 to 5 stats.

checklist

{
  "_type": "checklist",
  "title": "Before you publish",
  "items": [
    { "text": "Primary keyword in the title", "checked": true },
    { "text": "Two internal links in" }
  ]
}

faq

{
  "_type": "faq",
  "items": [
    { "question": "What is included?", "answer": "Planning, research and publishing." }
  ]
}

prosCons

{
  "_type": "prosCons",
  "pros": ["Saves time", "Consistent voice"],
  "cons": ["Needs an AI subscription"]
}

howItWorks

{
  "_type": "howItWorks",
  "steps": [
    { "title": "Connect your AI", "description": "Add the connector in Claude or ChatGPT." },
    { "title": "Pick a site", "description": "Or create one." }
  ]
}

cta

{
  "_type": "cta",
  "title": "Ready to start?",
  "description": "Plan your first topic in five minutes.",
  "buttonText": "Get started",
  "buttonUrl": "https://example.com/signup"
}

title, buttonText and buttonUrl are required.

linkGrid

{
  "_type": "linkGrid",
  "columns": 3,
  "items": [
    { "title": "Getting started", "subtitle": "Read first", "url": "/blog/start", "icon": "rocket" }
  ]
}

columns is 2, 3 (default) or 4.

featureGrid

{
  "_type": "featureGrid",
  "columns": 2,
  "items": [
    {
      "title": "Research",
      "description": "Facts and opinions from the web, with sources.",
      "icon": "search",
      "items": ["Web", "Reddit"]
    }
  ]
}

columns is 1, 2 (default) or 3.

featureList

{
  "_type": "featureList",
  "items": [
    { "title": "Internal links", "description": "Resolved at publish.", "icon": "link" }
  ]
}

Icons

icon strings name an icon from a set your site chooses (for example Lucide). Render the block without the icon when you don't know the name.

Site-specific blocks

The JSON schema also defines blocks that only some receivers render:

  • hubListing: { "_type": "hubListing" }, optionally with size ("small" or "large"), m, l, d and q to freeze a search. It marks the spot where your site inserts a live listing of its own (at most one per article). If you have nothing to place there, render nothing: no heading, no empty state, no spacing.
  • priceComparison, seasonCalendar, searchBox: used by receivers that declare them through components-condensed.

If you don't implement one of these, skip it like any unknown type. It must never show as visible output.

On this page