Search the Qalma docs

Type at least two characters to get started.

Docs

Content & Serialization

Qalma parses initial HTML into a ProseMirror document using the schema produced by your selected plugins, and can serialize that document back out in three formats.

const editor = createQalmaEditor({
  content: '<h2>Plan</h2><p><strong>Ship the docs.</strong></p>',
  plugins: [HeadingsPlugin, ...TextFormattingKit],
});

Output formats

Format Read Write Lossless Best for
HTML html() signal setHtml() Yes Rendering, interop with existing HTML
JSON getJSON() setJSON() Yes Persisting and restoring documents
Markdown getMarkdown() — (input rules) No Exporting to Markdown, plain-text storage

JSON is ProseMirror's native document model and the recommended format to persist content. Markdown is output-only: typing Markdown syntax is handled by each plugin's input rules, not by a Markdown parser.

Initial content

content is optional. If you omit it, Qalma starts from '<p></p>'.

const emptyEditor = createQalmaEditor();

When the editor mounts, the controller parses editor.html() with the active schema and creates the ProseMirror state. Unsupported tags are handled by ProseMirror's DOM parser according to the current schema. For example, a document can only keep headings when HeadingsPlugin is present.

const editor = createQalmaEditor({
  content: '<h1>Kept as a heading</h1>',
  plugins: [HeadingsPlugin],
});

Serialized HTML signal

editor.html is an Angular signal. It updates after transactions that change the document.

<qalma-editor [editor]="editor">
  <qalma-content class="block min-h-40 p-4 [&_.ProseMirror]:outline-none" />
</qalma-editor>

<pre><code>{{ editor.html() }}</code></pre>

Qalma serializes the document content, not the outer doc node. Newline characters inside serialized output are escaped as .

Replacing content

Use setHtml(html) when the app loads a saved document or restores a draft.

loadSavedDocument(html: string): void {
  this.editor.setHtml(html);
  this.editor.focus();
}

Before the content surface is mounted, setHtml() updates only the signal. After mount, it rebuilds the editor state with the same schema, plugins, and base keymaps.

Saving content

Because html is a signal, you can bind it directly or react to it with Angular reactivity.

import { computed, effect } from '@angular/core';

readonly hasContent = computed(() => !this.editor.isEmpty());

constructor() {
  effect(() => {
    localStorage.setItem('qalma-draft', this.editor.html());
  });
}

isEmpty() is the canonical "is there anything here?" check: it ignores empty paragraphs and hard breaks, still counts media such as images and tables, and stays reactive inside computed. See Editor & Controller.

For form-specific synchronization, use the adapter in Forms Integration so Angular owns validation, disabled state, touched state, and resets.

JSON

getJSON() returns the document as ProseMirror's native JSON — the lossless format to persist. setJSON() restores it. Both work before and after the content surface mounts.

import { QalmaDocument } from '@qalma/editor';

saveDraft(): void {
  const doc: QalmaDocument = this.editor.getJSON();
  localStorage.setItem('qalma-draft', JSON.stringify(doc));
}

restoreDraft(): void {
  const raw = localStorage.getItem('qalma-draft');

  if (raw) {
    this.editor.setJSON(JSON.parse(raw) as QalmaDocument);
  }
}

Unlike HTML, JSON preserves every node attribute and mark exactly, so a getJSON()setJSON() round-trip reproduces the document without loss. Prefer it over HTML when you control both the save and load sides.

Markdown

getMarkdown() serializes the document to Markdown (CommonMark plus GitHub-Flavored extensions: tables, task lists, and strikethrough).

exportMarkdown(): string {
  return this.editor.getMarkdown();
}

Marks and nodes that Markdown cannot express fall back to inline HTML — which CommonMark permits — so the output stays complete instead of silently dropping content:

Content Markdown output
Bold, italic, strikethrough **b**, *i*, ~~s~~
Inline code, code block `code`, fenced ``` with language
Links, images [text](href), ![alt](src)
Headings, blockquote, lists, task lists, tables #, >, -/1., - [x], GFM pipe table
Underline <u>...</u>
Monospace <span data-qalma-monospace="">...</span>
Subscript / superscript <sub>...</sub> / <sup>...</sup>
Text color / highlight <span style> / <mark>
Mention the mention label as plain text

Markdown is output-only. To restore content, persist JSON instead; to let users type Markdown shortcuts, rely on each plugin's input rules.

Plugin-owned serialization

Each plugin decides how its node or mark parses and serializes:

Plugin HTML surface
TextFormattingKit <strong>, <em>, <u>, <s>
InlineCodePlugin <code>
MonospacePlugin <span data-qalma-monospace="">
HeadingsPlugin <h1> through configured levels
ListsPlugin <ul>, <ol>, <li>
BlockquotePlugin <blockquote>
CodeBlockPlugin <pre><code class="language-id">...</code></pre>
LinkPlugin <a href="..." target="..." rel="...">
ImagePlugin inline <img src="..." alt="..." title="...">
MentionPlugin <span data-qalma-mention ...>
ColorPlugin <span style="color: ...; background-color: ...">
HighlightPlugin <mark> or <mark style="background-color: ...">
TextAlignPlugin style="text-align: ..." on configured nodes, except left alignment serializes as no attribute.

Read-only content

setEditable(false) leaves serialization active. The editor still exposes html(), query(), and command state, but command execution and canExecute() return false while the controller is read-only.