Routing rule (READ FIRST). When hand-authoring or returning Ricos / richContent JSON, use this recipe — the shapes, nesting rules, spacer paragraphs, and plugin nodes below. Do not rely on API schema search alone; it misses those details. After this recipe loads, do not perform additional schema or documentation searches for node types covered here; compose and self-audit the JSON from this file. When the user only wants JSON output, do not call Ricos convert/validate APIs.
Ricos is Wix's rich-content format — a tree of typed nodes serialized as JSON. The same structure is embedded by many products: a Blog post's draftPost.richContent, a Store product's rich description, an Events description, and CMS rich-text fields all expect a Ricos document. This recipe is the authoring reference for that node tree: the valid shape of each node, how nodes nest, and how to format text. It is intentionally product-agnostic — the consuming API decides where the document goes; this recipe governs what a valid document looks like.
A Ricos document is an object with a nodes array: { "nodes": [ /* block nodes */ ] }. Whatever field the consuming API exposes (e.g. richContent), it holds this object. For validating or converting an existing document to/from HTML/Markdown, see Ricos Converter Service.
type is always a bare string — "type": "PARAGRAPH", never an object like "type": { "type": "PARAGRAPH" }. An object-valued type may pass a shallow validation but renders as a broken/uneditable block.type, an optional id, and (for container nodes) a nodes array of children. Node ids are optional when authoring for a create request — the API generates them; the examples below omit id for brevity.PARAGRAPH, HEADING, or CODE_BLOCK. It must never sit directly in the root nodes array or inside a LIST_ITEM, BLOCKQUOTE, or TABLE_CELL — those must contain a PARAGRAPH (or HEADING) that then contains the TEXT. See Nesting rules.PARAGRAPH spacer nodes between sibling blocks when you need breathing room. See Vertical spacing between blocks.Use this checklist while composing and again before returning JSON. Treat every path below as required for the described request:
BUTTON, never a PARAGRAPH with an inline LINK. Include buttonData.type: "LINK", buttonData.text, buttonData.link.url, buttonData.link.target, and buttonData.containerData.{ "type": "AUDIO", "nodes": [], "audioData": { ... } }. The empty nodes array is on the AUDIO node beside audioData — never inside audioData. Include audioData.containerData.alignment, width, and textWrap, plus audioData.audio.src.id.videoData.video.src and videoData.video.duration inside video; place videoData.thumbnail beside video, with src.id, width, and height.GALLERY. Put images in galleryData.items, not as child IMAGE nodes. Every item uses image.media with src, width, and height; include galleryData.options.layout.type and numberOfColumns.COLLAPSIBLE_LIST → COLLAPSIBLE_ITEM → COLLAPSIBLE_ITEM_TITLE | COLLAPSIBLE_ITEM_BODY → PARAGRAPH → TEXT. Every PARAGRAPH includes paragraphData, and every item includes both title and body.HTML with htmlData.source, either url or html, and htmlData.containerData.width, height, and alignment.The renderer does not add margin between stacked block nodes. Adjacent paragraphs, headings, images, videos, tables, collapsible lists, buttons, galleries, and other blocks will sit flush against each other unless you separate them explicitly.
Use an empty PARAGRAPH as a spacer — a paragraph with no nodes (no TEXT children):
Place spacer paragraphs at the root nodes level (or inside a CARD page) wherever layout needs vertical rhythm:
When the user requests only specific nodes in one JSON output (e.g. "a blockquote and then a paragraph"), include exactly those nodes — do not add spacer paragraphs they did not ask for.
Do not rely on \n inside textData.text for spacing — that does not create a real line break between blocks. Do not expect paragraphData margins or containerData alone to separate unrelated sibling nodes; the empty paragraph is the supported spacing mechanism.
Example — spacer after an image and before a table:
PARAGRAPH — the base text container. An empty paragraph — { "type": "PARAGRAPH" } — is the vertical spacer between block nodes (the renderer adds no gap on its own). See Vertical spacing between blocks. paragraphData.textStyle.textAlignment accepts AUTO·LEFT·CENTER·RIGHT·JUSTIFY:
HEADING — same TEXT-in-container shape as PARAGRAPH, with the level (1–6) in headingData:
BULLETED_LIST / ORDERED_LIST — nesting is LIST → LIST_ITEM → PARAGRAPH → TEXT. Ordered lists use orderedListData in place of bulletedListData:
BLOCKQUOTE — wraps a PARAGRAPH (never a bare TEXT):
DIVIDER — a standalone horizontal rule (no children). lineStyle: SINGLE·DOUBLE·DASHED·DOTTED; width: LARGE·MEDIUM·SMALL:
TABLE — nesting is TABLE → TABLE_ROW → TABLE_CELL → PARAGRAPH → TEXT. tableData.dimensions.colsWidthRatio sets relative column widths. Fill a header row or zebra-stripe body rows with tableCellData.cellStyle.backgroundColor (a hex string):
CODE_BLOCK — children are TEXT nodes (one per line, or \n-joined):
IMAGE — references a Wix Media id (upload/import the image first via Media Manager; a raw external URL will not render). Requires width and height. An optional CAPTION child holds a TEXT node:
BUTTON — a standalone call-to-action block. Not the same as an inline LINK decoration on TEXT (that is hyperlinked body copy; this is a labeled button control). buttonData.type is required on every BUTTON node — use "LINK" for URL navigation or "ACTION" for viewer click handlers (never omit type even when link is present). Two buttonData.type values:
LINK — navigates to a URL when clicked (link.url, link.target: BLANK·SELF, optional link.rel).ACTION — triggers a viewer onClick handler (store the node in JSON; behavior is configured in the Ricos viewer, not in the document body).AUDIO — uploaded audio via Wix Media src.id, or an embed variant whose audioData.html holds an iframe (SoundCloud, Spotify). Include audioData.containerData with alignment and width settings, and use an empty nodes: [] array because AUDIO has no child nodes. Optional fields include coverImage (src.id, width, height), name, authorName, and disableDownload:
VIDEO — Wix Media video.src.id, duration (seconds), and a thumbnail (src.id, width, height). YouTube and other embeds use the same VIDEO node shape with the appropriate media id:
GALLERY — multiple images in one block. Each items[] entry wraps image.media with src (id or url), width, and height. galleryData.options controls layout (layout.type: GRID, numberOfColumns, orientation; item.ratio, item.crop; thumbnails.placement):
COLLAPSIBLE_LIST — FAQ-style expandable items. Nesting is COLLAPSIBLE_LIST → COLLAPSIBLE_ITEM → COLLAPSIBLE_ITEM_TITLE | COLLAPSIBLE_ITEM_BODY → PARAGRAPH → TEXT (body cells may also contain other supported block plugins such as IMAGE or VIDEO). The item wrapper type is COLLAPSIBLE_ITEM — not COLLAPSIBLE_LIST_ITEM or other variants:
initialExpandedItems: NONE·FIRST·ALL. Set isQapageData: true in list metadata when the list should render as FAQ structured data in search results.
HTML — embeds external content in an iframe. Provide either url (page URL) or inline html markup; source is typically "HTML". Size via containerData.width / height (custom pixel strings or size):
Apply formatting with the decorations array on a TEXT node. Each decoration is an object with a type and (for some types) a data field:
| Decoration | Data field |
|---|---|
BOLD | fontWeightValue: 700 |
ITALIC | italicData: true |
UNDERLINE | (none) |
STRIKETHROUGH | strikethroughData: true |
COLOR | colorData: { foreground: "#hex" } (add background for highlight) |
LINK | linkData: { link: { url, target: "BLANK" } } |
FONT_SIZE | fontSizeData: { unit: "PX", value: 24 } |
SPOILER | (none — { "type": "SPOILER" } hides text behind a reveal control) |
foreground for colors.\n inside textData.text — one visual line is one node. Emit separate sibling PARAGRAPH/HEADING nodes for separate lines.Assemble the shapes above into one valid richContent document. This example exercises every common node type — heading, bulleted list, ordered list, blockquote, filled-header table, divider, code block, and a paragraph with mixed bold + link runs — all correctly nested. Copy its structure; replace the placeholder text with real content.
Note the mixed-run paragraph at the end: the linked words are their own TEXT node carrying BOLD + LINK, while the surrounding words are separate plain TEXT runs — that is how you apply formatting to part of a sentence.
| Parent | Valid children |
|---|---|
Root nodes | PARAGRAPH, HEADING, BULLETED_LIST, ORDERED_LIST, BLOCKQUOTE, DIVIDER, IMAGE, TABLE, CODE_BLOCK, BUTTON, AUDIO, VIDEO, GALLERY, COLLAPSIBLE_LIST, HTML |
| PARAGRAPH / HEADING / CODE_BLOCK | TEXT |
| BULLETED_LIST / ORDERED_LIST | LIST_ITEM |
| LIST_ITEM / BLOCKQUOTE | PARAGRAPH (which then contains TEXT) |
| TABLE → TABLE_ROW → TABLE_CELL | cell contains PARAGRAPH / HEADING / IMAGE |
| IMAGE / VIDEO | CAPTION (optional) |
| COLLAPSIBLE_LIST | COLLAPSIBLE_ITEM |
| COLLAPSIBLE_ITEM | COLLAPSIBLE_ITEM_TITLE, COLLAPSIBLE_ITEM_BODY |
| COLLAPSIBLE_ITEM_TITLE / _BODY | PARAGRAPH (and other plugins supported inside collapsible cells — see COLLAPSIBLE_LIST above) |
| BUTTON / AUDIO / GALLERY / HTML | (leaf — nodes: [] or omit) |
All decidable from the JSON itself — check before handing the document to a consuming API:
type is a bare string — search for "type": {; there should be zero hits.LIST_ITEM, a BLOCKQUOTE, or a TABLE_CELL.LIST → LIST_ITEM → PARAGRAPH → TEXT and TABLE → TABLE_ROW → TABLE_CELL → PARAGRAPH → TEXT, no level skipped.level (1–6) and nest logically (don't jump H2 → H4).\n inside textData.text — split into sibling nodes; mixed inline formatting → split into multiple TEXT runs.id (not a raw URL), with width, height, and meaningful altText.LINK decoration has a valid url and target. Use a BUTTON block with buttonData.type: "LINK" for CTAs — not an inline LINK decoration styled to look like a button.buttonData.type (LINK requires link.url; ACTION has no URL). Omitting type is invalid even when link is set. LINK buttons and inline LINK decorations serve different purposes.nodes: [] and audioData.containerData; VIDEO keeps thumbnail beside video under videoData; every GALLERY item is under galleryData.items and carries image.media.src, width, and height._TITLE and _BODY, each wrapping content through PARAGRAPH → TEXT, and every PARAGRAPH has paragraphData.htmlData includes source, containerData.width, containerData.height, and either url or html (not both empty).SPOILER is a decoration on TEXT (or containerData.spoiler on some block plugins in editor output); preserve it verbatim on edit.{ "type": "PARAGRAPH" } spacers — especially after images, videos, tables, collapsible lists, galleries, buttons, and between consecutive paragraphs. Missing spacers produce a wall-of-blocks layout with no renderer-added margin.Last updated: 20 August 2026