Pure Elixir utilities for working with TipTap JSON content.
TipTap stores documents as a JSON tree of typed nodes with marks. This module provides server-side operations — HTML rendering, plain-text extraction, validation, and sanitization — so you can process editor content without JavaScript.
TipTap JSON Format
%{
"type" => "doc",
"content" => [
%{
"type" => "paragraph",
"content" => [
%{"type" => "text", "text" => "Hello ", "marks" => [%{"type" => "bold"}]},
%{"type" => "text", "text" => "world"}
]
}
]
}Usage
alias PhiaUi.Components.Editor.TipTapHelpers
# Render JSON to safe HTML
html = TipTapHelpers.content_to_html(json)
# Extract plain text for search indexing
text = TipTapHelpers.content_to_text(json)
# Validate against allowed schema
{:ok, json} = TipTapHelpers.validate_content(json, allowed_nodes: ~w(paragraph heading))
# Count words
count = TipTapHelpers.word_count(json)
Summary
Functions
Converts a TipTap JSON document to an HTML string.
Extracts plain text from a TipTap JSON document.
Returns the canonical empty TipTap document.
Strips disallowed nodes and marks from a TipTap JSON document.
Validates a TipTap JSON document against allowed node and mark types.
Counts words in a TipTap JSON document.
Functions
Converts a TipTap JSON document to an HTML string.
The output is not marked as safe — use Phoenix.HTML.raw/1 when rendering
in templates, after sanitizing user content.
Examples
iex> doc = %{"type" => "doc", "content" => [
...> %{"type" => "paragraph", "content" => [
...> %{"type" => "text", "text" => "Hello"}
...> ]}
...> ]}
iex> TipTapHelpers.content_to_html(doc)
"<p>Hello</p>"
Extracts plain text from a TipTap JSON document.
Block-level nodes are separated by newlines. Useful for search indexing, word counting, and preview generation.
Examples
iex> doc = %{"type" => "doc", "content" => [
...> %{"type" => "paragraph", "content" => [
...> %{"type" => "text", "text" => "Hello world"}
...> ]},
...> %{"type" => "paragraph", "content" => [
...> %{"type" => "text", "text" => "Second paragraph"}
...> ]}
...> ]}
iex> TipTapHelpers.content_to_text(doc)
"Hello world\nSecond paragraph"
@spec empty_document() :: map()
Returns the canonical empty TipTap document.
Example
iex> TipTapHelpers.empty_document()
%{"type" => "doc", "content" => [%{"type" => "paragraph"}]}
Strips disallowed nodes and marks from a TipTap JSON document.
Nodes with disallowed types are removed entirely. Marks with disallowed types are stripped from text nodes (text is kept). Returns the cleaned document.
Options
:allowed_nodes— list of allowed node type strings:allowed_marks— list of allowed mark type strings
Examples
iex> doc = %{"type" => "doc", "content" => [
...> %{"type" => "paragraph", "content" => [%{"type" => "text", "text" => "ok"}]},
...> %{"type" => "script", "content" => []}
...> ]}
iex> TipTapHelpers.sanitize_content(doc)
%{"type" => "doc", "content" => [
%{"type" => "paragraph", "content" => [%{"type" => "text", "text" => "ok"}]}
]}
Validates a TipTap JSON document against allowed node and mark types.
Returns {:ok, doc} if valid, or {:error, reasons} with a list of issues.
Options
:allowed_nodes— list of allowed node type strings (default: standard set):allowed_marks— list of allowed mark type strings (default: standard set)
Examples
iex> doc = %{"type" => "doc", "content" => [%{"type" => "paragraph"}]}
iex> TipTapHelpers.validate_content(doc)
{:ok, doc}
iex> doc = %{"type" => "doc", "content" => [%{"type" => "script"}]}
iex> TipTapHelpers.validate_content(doc)
{:error, ["disallowed node type: script"]}
@spec word_count(map() | nil) :: non_neg_integer()
Counts words in a TipTap JSON document.
Examples
iex> doc = %{"type" => "doc", "content" => [
...> %{"type" => "paragraph", "content" => [
...> %{"type" => "text", "text" => "Hello beautiful world"}
...> ]}
...> ]}
iex> TipTapHelpers.word_count(doc)
3