New Froala Editor v5.3.1 is here – Learn More
Froala Documentation
- Installation Guides
- Browser Support
- Languages Support
- Shortcuts
- Activation
- Examples
- Customize the Editor
- Use-cases
- Plugins
- APIs
- Development Frameworks
- Server Integrations
- Server SDKs
- Migration Guides
- Changelog
- Tutorials
- Froala Docs
- /
- Migration Guides
- /
- Migrate from ProseMirror to Froala
Migrate from ProseMirror to Froala
This guide maps the current ProseMirror toolkit (the prosemirror-state, prosemirror-view, and prosemirror-model modules) to the Froala WYSIWYG Editor. You get side-by-side code for every step of the move: initialization, configuration, plugins, reading and writing content, events, methods, image upload, and React, so you can swap editors without relearning rich text from scratch.
What you need to know first
ProseMirror and Froala solve the same problem from opposite directions. ProseMirror is a toolkit: you define a Schema that lists every node and mark your document may contain, build an immutable EditorState, render it through an EditorView, and route every change through a Transaction. Nothing (not even a toolbar or the Enter key) exists until you wire it in. The document is a custom tree, not HTML, and you serialize to HTML only when you ask for it.
Froala inverts that model. You point new FroalaEditor() at an element, pass a plain options object, and a fully featured editor appears: toolbar, 45+ plugins, image handling, and clean HTML in and out. The document is HTML, so editor.html.get() and editor.html.set() are all you need to move content around. You trade ProseMirror's fine-grained control over the document model for speed of integration and a batteries-included feature set.
Licensing also differs. ProseMirror is open source under the MIT license and free to use. Froala is a commercial editor: you activate it with a license key option, which also unlocks support and updates. Plan to add your key during initialization.
| Concept | ProseMirror | Froala |
|---|---|---|
| Initialization | new EditorView(mount, { state }) | new FroalaEditor('#editor', options) |
| Element targeting | A mount DOM node passed to the view; ProseMirror renders the editable surface inside it | CSS selector or DOM node: div, textarea, span, even an img |
| Enabling features | Hand-built Schema plus a composed plugins array (history, keymap, menu) | 45+ pluginsEnabled plugins |
| Toolbar | Not built in, supplied by prosemirror-menu / prosemirror-example-setup | toolbarButtons array of button groups |
| Reading content | DOMSerializer on view.state.doc, or doc.toJSON() | editor.html.get() |
| Writing content | DOMParser.parse(), then dispatch a replace transaction | editor.html.set(html) |
| Events | dispatchTransaction prop and plugin props | events: { contentChanged: … } |
| Teardown | view.destroy() | editor.destroy() |
| Licensing | MIT, open source (free) | Commercial license key (key option) |
The one mental shift: stop thinking in terms of a schema, state, and transactions you assemble and maintain yourself. In Froala you point at an element, pass an options object, and read or write HTML directly. The editor owns the document model so you can focus on your application.
Swap ProseMirror for Froala in three steps
Remove the ProseMirror module stack, add the single Froala bundle, then initialize. Most apps complete the swap in well under an hour.
Step 1: Remove ProseMirror
ProseMirror is assembled from many small packages and almost always bundled. Delete the imports and the state/view setup, and drop the dependencies from package.json.
// Remove the module imports…
import {EditorState} from "prosemirror-state"
import {EditorView} from "prosemirror-view"
import {Schema, DOMParser, DOMSerializer} from "prosemirror-model"
import {schema} from "prosemirror-schema-basic"
import {exampleSetup} from "prosemirror-example-setup"
// …and drop the packages from package.json:
// prosemirror-state, prosemirror-view, prosemirror-model,
// prosemirror-schema-basic, prosemirror-schema-list,
// prosemirror-example-setup, prosemirror-commands,
// prosemirror-keymap, prosemirror-history
Step 2 — Add Froala
Froala ships as one packaged build. Use the .pkgd files from the jsDelivr CDN for a quick start, or install the npm package for a bundled app. The packaged build already includes the 45+ official plugins.
<link href="https://cdn.jsdelivr.net/npm/froala-editor@latest/css/froala_editor.pkgd.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/froala-editor@latest/js/froala_editor.pkgd.min.js"></script>
# One package, all plugins bundled
npm install froala-editor
Step 3 — Initialize the editor
ProseMirror needs a schema, a state, and a view before anything is editable. Froala needs one constructor call. Whatever HTML already lives inside the target element becomes the editor's initial content, and you add your license key here.
import {EditorState} from "prosemirror-state"
import {EditorView} from "prosemirror-view"
import {DOMParser} from "prosemirror-model"
import {schema} from "prosemirror-schema-basic"
import {exampleSetup} from "prosemirror-example-setup"
const mount = document.querySelector("#editor")
const view = new EditorView(mount, {
state: EditorState.create({
doc: DOMParser.fromSchema(schema).parse(mount),
plugins: exampleSetup({schema})
})
})
const editor = new FroalaEditor('#editor', {
key: 'YOUR_LICENSE_KEY'
});
// Froala accepts a CSS selector or a DOM node, and works on
// a div, textarea, span, or even an img element.
const onNode = new FroalaEditor(document.getElementById('editor'));
Configuration and options
In ProseMirror, behavior is configured by composing a plugins array and a custom Schema, plus view-level props. In Froala, behavior is a flat options object passed to the constructor. The most common settings map cleanly.
// Placeholder, height, and toolbar are all opt-in pieces.
import {placeholder} from "./placeholder-plugin" // custom
const state = EditorState.create({
schema,
plugins: [
...exampleSetup({schema, menuBar: true}),
placeholder("Start typing…")
]
})
const view = new EditorView(mount, {
state,
attributes: { style: "min-height: 200px" }
})
const editor = new FroalaEditor('#editor', {
key: 'YOUR_LICENSE_KEY',
placeholderText: 'Start typing…',
heightMin: 200,
charCounterCount: true,
toolbarButtons: [
['bold', 'italic', 'underline'],
['formatOL', 'formatUL'],
['insertLink', 'insertImage'],
['undo', 'redo']
]
});
Browse the full list of settings in the Froala options reference.
Plugins
ProseMirror plugins are JavaScript objects you instantiate and add to the state's plugins array. Order matters, because props are resolved in sequence. Froala bundles 45+ plugins in the packaged build; you control which are active with pluginsEnabled, or use the inverse pluginsDisabled to keep everything except a few.
import {history, undo, redo} from "prosemirror-history"
import {keymap} from "prosemirror-keymap"
import {baseKeymap} from "prosemirror-commands"
const state = EditorState.create({
schema,
plugins: [
history(),
keymap({"Mod-z": undo, "Mod-y": redo}),
keymap(baseKeymap)
]
})
// Turn ON only the plugins you want…
new FroalaEditor('#editor', {
pluginsEnabled: ['align', 'lists', 'link', 'image', 'table']
});
// …or keep everything EXCEPT a few (the inverse).
new FroalaEditor('#editor', {
pluginsDisabled: ['video', 'emoticons']
});
Getting and setting content
This is where the two models feel most different. ProseMirror stores a custom document tree, so reading HTML means serializing the doc, and writing HTML means parsing it back and dispatching a replace transaction. Froala stores HTML, so get and set are one-liners.
Reading content
import {DOMSerializer} from "prosemirror-model"
// Serialize the document fragment to an HTML string.
function getHTML(view) {
const serializer = DOMSerializer.fromSchema(view.state.schema)
const fragment = serializer.serializeFragment(view.state.doc.content)
const div = document.createElement("div")
div.appendChild(fragment)
return div.innerHTML
}
// Or keep the native JSON representation:
const json = view.state.doc.toJSON()
const html = editor.html.get();
Writing content
import {DOMParser} from "prosemirror-model"
// Parse an HTML string and replace the whole document.
function setHTML(view, html) {
const tmp = document.createElement("div")
tmp.innerHTML = html
const doc = DOMParser.fromSchema(view.state.schema).parse(tmp)
const tr = view.state.tr.replaceWith(0, view.state.doc.content.size, doc.content)
view.dispatch(tr)
}
editor.html.set('<p>Hello <strong>Froala</strong>!</p>');
Events
ProseMirror surfaces changes through the dispatchTransaction prop: you intercept every transaction, apply it, and react. Froala exposes named events you register in the events option (or later with editor.events.on()). The closest Froala equivalent of "the document changed" is contentChanged.
const view = new EditorView(mount, {
state,
dispatchTransaction(tr) {
const next = view.state.apply(tr)
view.updateState(next)
if (tr.docChanged) {
console.log("Document changed")
}
}
})
new FroalaEditor('#editor', {
events: {
initialized: function () {
console.log('Editor ready');
},
contentChanged: function () {
console.log('Document changed', this.html.get());
}
}
});
See every hook in the Froala events reference.
Methods
Common imperative actions (read-only mode, focus, and teardown) exist in both editors. ProseMirror expresses read-only through the editable prop, which you update with setProps; Froala has dedicated methods.
// Read-only: toggle the editable prop.
view.setProps({ editable: () => false })
view.setProps({ editable: () => true })
// Focus the editing surface.
view.focus()
// Tear the editor down.
view.destroy()
// Read-only: turn editing off and on.
editor.edit.off();
editor.edit.on();
// Focus the editing surface.
editor.events.focus();
// Tear the editor down.
editor.destroy();
The complete list lives in the Froala methods reference.
Image upload
ProseMirror has no built-in upload pipeline. You add an image node to your schema, intercept paste or drop events, upload the file yourself, then dispatch a transaction that inserts the node with the returned URL: all custom code. Froala ships an image plugin: set imageUploadURL (and any params), and the editor handles file selection, upload, and insertion, firing events you can hook into.
// You wire upload + insertion yourself, e.g. on paste.
const view = new EditorView(mount, {
state,
props: {
handlePaste(view, event) {
const file = event.clipboardData?.files?.[0]
if (!file) return false
uploadFile(file).then(url => {
const node = view.state.schema.nodes.image.create({src: url})
view.dispatch(view.state.tr.replaceSelectionWith(node))
})
return true
}
}
})
async function uploadFile(file) {
const body = new FormData()
body.append("file", file)
const res = await fetch("/upload", {method: "POST", body})
return (await res.json()).link
}
new FroalaEditor('#editor', {
imageUploadURL: '/upload',
imageUploadMethod: 'POST',
imageUploadParams: { folder: 'posts' },
events: {
'image.uploaded': function (response) {
console.log('Uploaded', response);
},
'image.error': function (error, response) {
console.error('Upload failed', error);
}
}
});
Froala expects the upload endpoint to return JSON containing a link field with the stored image URL.
Framework integration: React
ProseMirror has no official React component, so the standard pattern is to create the EditorView inside a useEffect and destroy it on cleanup, holding the view in a ref. Froala provides the react-froala-wysiwyg package: a controlled component bound with model and onModelChange.
import {useEffect, useRef} from "react"
import {EditorState} from "prosemirror-state"
import {EditorView} from "prosemirror-view"
import {schema} from "prosemirror-schema-basic"
import {exampleSetup} from "prosemirror-example-setup"
function Editor() {
const ref = useRef(null)
useEffect(() => {
const view = new EditorView(ref.current, {
state: EditorState.create({
schema, plugins: exampleSetup({schema})
})
})
return () => view.destroy()
}, [])
return <div ref={ref} />
}
import {useState} from "react"
import FroalaEditor from "react-froala-wysiwyg"
import "froala-editor/css/froala_style.min.css"
import "froala-editor/css/froala_editor.pkgd.min.css"
import "froala-editor/js/plugins.pkgd.min.js"
function Editor() {
const [content, setContent] = useState("")
return (
<FroalaEditor
tag="textarea"
model={content}
onModelChange={setContent}
config={{ key: "YOUR_LICENSE_KEY" }}
/>
)
}
You're ready to ship
You have mapped every core ProseMirror concept to its Froala equivalent. Add your license key, pick your toolbar and plugins, and point the editor at your content. Explore the references below to fine-tune the rest.
Do you think we can improve this article? Let us know.
Whats on this page hide