Editor.js for Retool: an open-source block editor component
reading · 00%

Editor.js for Retool: an open-source block editor component

Block-style editing in Retool, free to use and extend.

Published
Reading4 min
CategoryRetool
Words888

The knowledge base team writes articles inside a Retool app, and the website renders them. A plain text area loses headings and lists. An HTML field keeps them, and every downstream system then has to parse, clean, and sanitize markup before it can use the content. What the team needs is rich text stored as structured data.

Editor.js does that. It is a block-style editor whose output is JSON: one object per heading, paragraph, list, or quote. We packaged it as a Retool custom component, written in TypeScript, and published it on GitHub under the MIT license, free to use and extend.

What it does

  • Block editing. Headings (H1 to H4), paragraphs, ordered and unordered lists, quotes, code blocks, checklists, tables, delimiters, and warnings.

  • Inline formatting. Marker, underline, inline code, links, and footnotes.

  • Embeds. YouTube and CodePen.

  • JSON output. The content property holds the editor's state as a JSON string.

  • Tool switches. Each tool can be turned on or off in the inspector.

  • Styling. backgroundColor (default #f8fafc) and textColor (default #000).

Install and deploy

You need Node.js 20 or later and an API access token with read and write scopes for Custom Component Libraries.

Shell
git clone https://github.com/StackdropCO/editorjs-retool-component.git
cd editorjs-retool-component
npm install

Log in, create the component library in your Retool organization, and start development mode:

Shell
npx retool-ccl login
npx retool-ccl init
npx retool-ccl dev

When it is ready, deploy an immutable version:

Shell
npx retool-ccl deploy

Then open the Custom Component settings in your app and switch from dev to the version you just published. You may need to refresh the page before the new version appears.

Load and save content

Drag the Editor.js component onto the canvas. The content property works in both directions. Set it to load existing content, and read it to save what the user wrote. The component updates content as the user types.

In the examples below, the component is named editor1 and the content lives in a Postgres jsonb column. A query loads the selected article:

SQL
select body
from knowledge_articles
where id = {{ articlesTable.selectedRow.id }};

Bind content to that result, as a string:

JavaScript
{{ JSON.stringify(getArticle.data.body[0]) }}

A Save button runs the write query. Retool sends {{ }} values in a SQL query as prepared statement parameters, so the JSON arrives as one value:

SQL
update knowledge_articles
set body = {{ editor1.content }}::jsonb,
    updated_at = now()
where id = {{ articlesTable.selectedRow.id }};

For a new article, bind content to a temporary state variable instead, and insert on save.

What the data looks like

Each save stores a document like this one:

JSON
{
  "time": 1635603431943,
  "blocks": [
    {
      "id": "12345",
      "type": "header",
      "data": {
        "text": "Editor.js",
        "level": 2
      }
    },
    {
      "id": "67890",
      "type": "paragraph",
      "data": {
        "text": "This is a paragraph block."
      }
    }
  ],
  "version": "2.23.2"
}

Because every block has a type and its own data, the content is easy to query, version, and render elsewhere. A website can map header blocks to its own heading styles, with no HTML cleanup in between.

Limit the tools

Every tool has a switch in the inspector: enableHeader, enableList, enableQuote, enableChecklist, enableCode, enableInlineCode, enableMarker, enableDelimiter, enableEmbed, enableTable, enableWarning, enableLink, enableUnderline, and enableFootnotes.

Turn on only what the content needs. A knowledge base might allow headings, paragraphs, lists, and links, and nothing else. A smaller tool set keeps the editor simple for authors and the stored content consistent.

Running it in production

  • Version the content schema. Editor.js writes its version into every document. If you change the tool set later, plan a migration for older block types.

  • Sanitize when you render. The JSON is structured, and embedded content can still carry markup. Sanitize on the way to HTML, especially for embeds.

  • Keep the JSON as the source. If a fast front end needs HTML, render and cache it. The JSON stays the version you edit.

  • Add a preview. A panel that renders the blocks lets authors see the result before publishing.

  • Convert where consumers need it. If another system expects HTML or markdown, add a converter from the block JSON. Libraries exist for both.

  • Guard against empty saves. Before the update runs, check that the parsed content has at least one block, in the Save button's event handler. A blank editor should never overwrite an article:

JavaScript
// "Only run when" on the Save button's click handler
{{ JSON.parse(editor1.content || '{}').blocks?.length > 0 }}
  • Pin a published version. Production apps should never point at the dev version of the library.

What is next

On the roadmap, not committed:

  • Drag-and-drop block reordering.

  • Custom block types configured from the inspector, such as a callout box or an embedded chart.

  • A full-screen editing mode.

  • Starter templates for blog posts and knowledge base articles.

  • A companion component that renders the JSON for preview.

Our view

We recommend storing rich text as Editor.js JSON when more than one system reads the content, because each reader can render blocks its own way. For a single internal note field, Retool's built-in Rich Text Editor is simpler, because it needs no library to deploy or maintain.

Issues and pull requests are welcome. For AI-assisted writing inside an app, see our AI rich text editor component. What is a custom Retool component covers when building one is worth it.

Filed underRetool
Worth passing along? Share this note.
LinkedInX
Newsletter

Get the next one in your inbox

We write when something is worth reading. Subscribing means you are happy to hear from us when that happens. Leave any time. Privacy policy.

Vaggelis Kapetanakis

Keep readingRetool
Related notes

Still here? Good sign

More notes from real builds, written up for the next person who needs one.

All articles
ContactLet's chat
Let's chat

Tell us what
you're building

Describe the process that is slowing your team down: the inbox, the spreadsheet, the portal nobody owns. We'll come back with a concrete approach, a rough timeline, and, where it helps, a working prototype within weeks.

What happens next
  1. We read every messageAn architect reads what you sent before anyone replies.
  2. You hear back in ≤ 2 daysA short reply with first questions and a suggested next step.
  3. We scope and prototypeA scoping call, then a working surface, not a slide deck.
Where are you in the process?