Just went down a bit of a rabbit hole on this myself after not understanding how to save content into a Rich text “Blocks” field from the API
Populated an entry with all the existing blocks and converted resulting output into a typescript type as well as a JSON Schema.
Typescript Type
interface TextNode {
text: string;
type: 'text';
bold?: boolean;
underline?: boolean;
italic?: boolean;
strikethrough?: boolean;
code?: boolean;
}
interface LinkNode {
url: string;
type: 'link';
children: TextNode[];
}
interface ListItemNode {
type: 'list-item';
children: (TextNode | LinkNode)[];
}
interface ImageFormat {
ext: string;
url: string;
hash: string;
mime: string;
name: string;
path: null | string;
size: number;
width: number;
height: number;
}
interface ImageNode {
type: 'image';
image: {
ext: string;
url: string;
hash: string;
mime: string;
name: string;
size: number;
width: number;
height: number;
caption: string;
formats: {
large?: ImageFormat;
small?: ImageFormat;
medium?: ImageFormat;
thumbnail?: ImageFormat;
};
provider: string;
createdAt: string;
updatedAt: string;
previewUrl: null | string;
alternativeText: string;
provider_metadata: null | any;
};
children: TextNode[];
}
interface BlockNode {
type: 'heading' | 'paragraph' | 'list' | 'quote';
level?: number;
format?: 'unordered' | 'ordered';
children: (TextNode | LinkNode | ListItemNode | ImageNode)[];
}
type RichTextInput = BlockNode[];
JSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"level": {
"type": "integer"
},
"format": {
"type": "string"
},
"children": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/definitions/textNode"
},
{
"$ref": "#/definitions/linkNode"
},
{
"$ref": "#/definitions/listItemNode"
},
{
"$ref": "#/definitions/imageNode"
}
]
}
}
},
"required": ["type", "children"]
},
"definitions": {
"textNode": {
"type": "object",
"properties": {
"text": {
"type": "string"
},
"type": {
"type": "string"
},
"bold": {
"type": "boolean"
},
"underline": {
"type": "boolean"
},
"italic": {
"type": "boolean"
},
"strikethrough": {
"type": "boolean"
},
"code": {
"type": "boolean"
}
},
"required": ["text", "type"]
},
"linkNode": {
"type": "object",
"properties": {
"url": {
"type": "string"
},
"type": {
"type": "string"
},
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/textNode"
}
}
},
"required": ["url", "type", "children"]
},
"listItemNode": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"children": {
"type": "array",
"items": {
"oneOf": [
{
"$ref": "#/definitions/textNode"
},
{
"$ref": "#/definitions/linkNode"
}
]
}
}
},
"required": ["type", "children"]
},
"imageNode": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"image": {
"type": "object",
"properties": {
// Define image properties here
},
"required": [
// List of required image properties
]
},
"children": {
"type": "array",
"items": {
"$ref": "#/definitions/textNode"
}
}
},
"required": ["type", "image", "children"]
}
// TODO: Define remaining image block params...
}
}
“kitchen sink” content
field in Admin
Resulting API Response
[
{
"type": "heading",
"level": 1,
"children": [
{
"text": "Heading 1",
"type": "text"
}
]
},
{
"type": "heading",
"level": 2,
"children": [
{
"text": "Heading 2",
"type": "text"
}
]
},
{
"type": "heading",
"level": 3,
"children": [
{
"text": "Heading 3",
"type": "text"
}
]
},
{
"type": "paragraph",
"children": [
{
"text": "",
"type": "text"
}
]
},
{
"type": "paragraph",
"children": [
{
"text": "basic paragraph",
"type": "text"
}
]
},
{
"type": "paragraph",
"children": [
{
"bold": true,
"text": "this is the bold",
"type": "text"
}
]
},
{
"type": "paragraph",
"children": [
{
"text": "this is underlined",
"type": "text",
"underline": true
}
]
},
{
"type": "paragraph",
"children": [
{
"text": "this is an italic paragraph",
"type": "text",
"italic": true
}
]
},
{
"type": "paragraph",
"children": [
{
"text": "this has strikethrough",
"type": "text",
"strikethrough": true
}
]
},
{
"type": "paragraph",
"children": [
{
"code": true,
"text": "some code",
"type": "text"
}
]
},
{
"type": "paragraph",
"children": [
{
"text": "",
"type": "text"
},
{
"url": "https://google.com",
"type": "link",
"children": [
{
"text": "a link",
"type": "text"
}
]
},
{
"text": "",
"type": "text"
}
]
},
{
"type": "list",
"format": "unordered",
"children": [
{
"type": "list-item",
"children": [
{
"text": "bulleted",
"type": "text"
}
]
},
{
"type": "list-item",
"children": [
{
"text": "list",
"type": "text"
}
]
}
]
},
{
"type": "list",
"format": "ordered",
"children": [
{
"type": "list-item",
"children": [
{
"text": "numbered",
"type": "text"
}
]
},
{
"type": "list-item",
"children": [
{
"text": "list",
"type": "text"
}
]
}
]
},
{
"type": "quote",
"children": [
{
"text": "Quote content here",
"type": "text"
}
]
},
{
"type": "image",
"image": {
"ext": ".png",
"url": "http://localhost:1337/uploads/mascot_1_b49785a30c.png",
"hash": "mascot_1_b49785a30c",
"mime": "image/png",
"name": "mascot-1.png",
"size": 284.58,
"width": 1024,
"height": 1024,
"caption": "imagine caption",
"formats": {
"large": {
"ext": ".png",
"url": "/uploads/large_mascot_1_b49785a30c.png",
"hash": "large_mascot_1_b49785a30c",
"mime": "image/png",
"name": "large_mascot-1.png",
"path": null,
"size": 948.28,
"width": 1000,
"height": 1000
},
"small": {
"ext": ".png",
"url": "/uploads/small_mascot_1_b49785a30c.png",
"hash": "small_mascot_1_b49785a30c",
"mime": "image/png",
"name": "small_mascot-1.png",
"path": null,
"size": 267.64,
"width": 500,
"height": 500
},
"medium": {
"ext": ".png",
"url": "/uploads/medium_mascot_1_b49785a30c.png",
"hash": "medium_mascot_1_b49785a30c",
"mime": "image/png",
"name": "medium_mascot-1.png",
"path": null,
"size": 570.48,
"width": 750,
"height": 750
},
"thumbnail": {
"ext": ".png",
"url": "/uploads/thumbnail_mascot_1_b49785a30c.png",
"hash": "thumbnail_mascot_1_b49785a30c",
"mime": "image/png",
"name": "thumbnail_mascot-1.png",
"path": null,
"size": 31.83,
"width": 156,
"height": 156
}
},
"provider": "local",
"createdAt": "2024-01-18T03:02:24.519Z",
"updatedAt": "2024-01-18T03:03:02.023Z",
"previewUrl": null,
"alternativeText": "alt text",
"provider_metadata": null
},
"children": [
{
"text": "",
"type": "text"
}
]
}
]
Hope this helps!