editorjs-go is a flexible, type-safe, and extensible Go library for parsing and rendering Editor.js documents into HTML, Markdown, or any other output format.
- ✅ Type-safe decoding with generics (
Block[T]) - 🔌 Plugin-based architecture (register custom decoders/renderers)
- 🔄 Support for nested/recursive blocks
- 💡 Context-passing for render logic and helpers
- ⚙️ Zero-opinion on escaping/output format – you decide (HTML, Markdown, plaintext…)
go get github.com/christiandenisi/editorjs-gopackage main
import (
"fmt"
"github.com/christiandenisi/editorjs-go"
"html"
)
type ParagraphData struct {
Text string `json:"text"`
}
func RenderParagraph(b editorjs.Block[ParagraphData], ctx *editorjs.Context) (string, error) {
return "<p>" + html.EscapeString(b.Data.Text) + "</p>", nil
}
func main() {
jsonData := []byte(`{
"time": 1234567890,
"version": "2.27.0",
"blocks": [
{
"id": "abc",
"type": "paragraph",
"data": { "text": "Hello <world>" }
}
]
}`)
conv := editorjs.New()
editorjs.Register(conv, "paragraph", RenderParagraph)
out, err := conv.Convert(jsonData)
if err != nil {
panic(err)
}
fmt.Println(out)
}These plugins can be combined to render full Editor.js documents using editorjs-go:
| Plugin | Block Type | Output |
|---|---|---|
editorjs-go-paragraph |
paragraph |
<p> |
editorjs-go-header |
header |
<h1>–<h6> |
editorjs-go-list |
list |
<ul>, <ol> |
editorjs-go-quote |
quote |
<blockquote> |
editorjs-go-delimiter |
delimiter |
<hr> |
editorjs-go-code |
code |
<pre><code> |
type Block[T any] struct {
ID string
Type string
Data T
Tunes map[string]interface{}
}- The library decodes raw JSON
RawBlocks into strongly typedBlock[T]using registered decoder functions. - Each block type (e.g.
paragraph,quote,list) is mapped to a decoder + renderer.
You register a block type using:
func Register[T any](conv *Converter, blockType string, renderer Renderer[T])Render functions receive a *Context, giving access to:
type Context struct {
RenderBlock func(RawBlock) (string, error)
RenderBlocks func([]RawBlock) (string, error)
Converter *Converter
}Use this for recursive rendering or accessing other converters.
type QuoteData struct {
Items []editorjs.RawBlock `json:"items"`
}
func RenderQuote(b editorjs.Block[QuoteData], ctx *editorjs.Context) (string, error) {
inner, err := ctx.RenderBlocks(b.Data.Items)
if err != nil {
return "", err
}
return "<blockquote>" + inner + "</blockquote>", nil
}Each block needs:
- A data struct matching the expected JSON
- A renderer function with signature:
func(Block[T], *Context) (string, error)- Registration using
editorjs.Register(...)
Escaping is not handled automatically.
Each renderer decides whether to use escaping, and what kind (e.g. HTML, Markdown, plaintext):
html.EscapeString(...)This design allows editorjs-go to work for:
- HTML output
- Markdown generation
- Text-only rendering
- Custom formats (LaTeX, XML, etc.)
- Always escape content in HTML renderers unless intentionally inserting raw HTML
- Use
RawBlock.Typeto dynamically dispatch rendering - Prefer composition and reuse of renderer logic (e.g. block-with-children)
You can test block rendering by feeding JSON directly and comparing output.
html, err := converter.Convert([]byte(myJSON))
if html != expectedHTML {
t.Errorf("render mismatch")
}Input:
{
"time": 0,
"version": "2.27.0",
"blocks": [
{
"type": "quote",
"data": {
"items": [
{
"type": "paragraph",
"data": { "text": "Hello inside quote" }
}
]
}
}
]
}Rendered:
<blockquote><p>Hello inside quote</p></blockquote>- You must register all block types used in the document
- No default escaping or sanitization
- Rendering logic is fully manual (by design)
- Plugin-based renderer loader
- Optional escaping middleware
MIT – free to use, modify, and distribute.
