New Froala Editor v5.4.0 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
- /
- SDKs
- /
- Node JS
Node.JS SDK Froala Editor
Overview
The Froala Node.js SDK is the server-side companion to the Froala WYSIWYG Editor. It gives your Node.js/Express backend everything the editor needs to handle uploads, manage media, and power real-time collaboration, all from a single package (wysiwyg-editor-node-sdk) with no extra services to stand up.
The SDK covers two areas: file & image handling for storing and safeguarding editor content, and a real-time collaboration backend for multi-user editing, review, and versioning. Use only the pieces you need. Each capability is a small module you mount on your own server.
File & image handling
- Server uploads: accept image and file uploads from the editor and store them on your server.
- Amazon S3 uploads: send images and files straight to an S3 bucket with signed requests.
- Delete: remove previously uploaded images and files from the server.
- Image resize: resize uploaded images server-side before storing them.
- Validation: enforce allowed types and size limits for both images and files before they are saved.
- Image manager: back the editor's image manager with server-side listing and management of stored images.
- References: ready-made
Image,File, andS3helpers that wrap the common upload, validation, and storage options.
Real-time collaboration backend (New in v5.3.0)
- WebSocket relay (
Collaborative): groups clients by document and broadcasts live edits between peers; all sync/CRDT logic runs client-side with Yjs. - Suggestions & comments (
CollabPersistence): REST + SQLite persistence for track-change suggestions and inline comments, including replies and lifecycle status. - Version control (
VersionControl): capture named or automatic snapshots of document content and browse the full version history. - Async save (
AsyncSave): save and restore the latest content for offline (non-real-time) editing.
The three persistence modules are backed by SQLite (better-sqlite3, installed automatically) and can share one database file, so a complete collaboration backend runs on a single HTTP server.
Install the Froala Editor SDK Node package
NPM is the package manager for Node.JS. Get the Editor SDK installed running the command below.
npm install wysiwyg-editor-node-sdk
Import the SDK in your app
var FroalaEditor = require('PATH_TO_THE_SDK/lib/froalaEditor.js');
Example
Basic example for server.js
var http = require('http');
var express = require('express');
var app = express();
var bodyParser = require('body-parser');
var path = require('path');
var fs = require('fs');
var gm = require('gm').subClass({ imageMagick: true });
var FroalaEditor = require('../lib/froalaEditor.js');
var Collaborative = FroalaEditor.Collaborative;
var CollabPersistence = FroalaEditor.CollabPersistence;
var VersionControl = FroalaEditor.VersionControl;
var AsyncSave = FroalaEditor.AsyncSave;
// Permissive CORS for the dev environment so the editor's webpack dev server
// (port 8001) can hit the SDK's REST endpoints (port 3000).
// eslint-disable-next-line no-console
console.warn('[collab] WARNING: CORS is open (*) and routes have no authentication. Do not use this configuration in production.');
app.use('/collab', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,POST,PATCH,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') return res.sendStatus(204);
next();
});
app.use(express.static(__dirname + '/'));
app.use('/bower_components', express.static(path.join(__dirname, '../bower_components')));
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
// Attach suggestion + comment persistence routes.
CollabPersistence.attachRoutes(app, { dbPath: path.join(__dirname, '..', 'collab.db')});
// Attach version control routes (same SQLite file).
VersionControl.attachRoutes(app, { dbPath: path.join(__dirname, '..', 'collab.db')});
// Attach async-save routes — persists the latest editor content per room
// when the editor runs in async mode (no realTimeConfig.syncUrl).
AsyncSave.attachRoutes(app, { dbPath: path.join(__dirname, '..', 'collab.db')});
app.get('/', function (req, res) {
res.sendFile(__dirname + '/index.html');
});
app.post('/upload_image', function (req, res) {
FroalaEditor.Image.upload(req, '/uploads/', function (err, data) {
if (err) {
return res.send(JSON.stringify(err));
}
res.send(data);
});
});
app.post('/upload_video', function (req, res) {
FroalaEditor.Video.upload(req, '/uploads/', function (err, data) {
if (err) {
return res.send(JSON.stringify(err));
}
res.send(data);
});
});
app.post('/upload_image_resize', function (req, res) {
var options = {
resize: [300, 300]
};
FroalaEditor.Image.upload(req, '/uploads/', options, function (err, data) {
if (err) {
return res.send(JSON.stringify(err));
}
res.send(data);
});
});
app.post('/upload_image_validation', function (req, res) {
var options = {
fieldname: 'myImage',
validation: function (filePath, mimetype, callback) {
gm(filePath).size(function (err, value) {
if (err) {
return callback(err);
}
if (!value) {
return callback('Error occurred.');
}
if (value.width != value.height) {
return callback(null, false);
}
return callback(null, true);
});
}
};
FroalaEditor.Image.upload(req, '/uploads/', options, function (err, data) {
if (err) {
return res.send(JSON.stringify(err));
}
res.send(data);
});
});
app.post('/upload_file', function (req, res) {
var options = {
validation: null
};
FroalaEditor.File.upload(req, '/uploads/', options, function (err, data) {
if (err) {
return res.status(404).end(JSON.stringify(err));
}
res.send(data);
});
});
app.post('/upload_file_validation', function (req, res) {
var options = {
fieldname: 'myFile',
validation: function (filePath, mimetype, callback) {
fs.stat(filePath, function (err, stat) {
if (err) {
return callback(err);
}
if (stat.size > 10 * 1024 * 1024) {
// > 10M
return callback(null, false);
}
return callback(null, true);
});
}
};
FroalaEditor.File.upload(req, '/uploads/', options, function (err, data) {
if (err) {
return res.status(404).end(JSON.stringify(err));
}
res.send(data);
});
});
app.post('/delete_image', function (req, res) {
FroalaEditor.Image.delete(req.body.src, function (err) {
if (err) {
return res.status(404).end(JSON.stringify(err));
}
return res.end();
});
});
app.post('/delete_video', function (req, res) {
FroalaEditor.Video.delete(req.body.src, function (err) {
if (err) {
return res.status(404).end(JSON.stringify(err));
}
return res.end();
});
});
app.post('/delete_file', function (req, res) {
FroalaEditor.File.delete(req.body.src, function (err) {
if (err) {
return res.status(404).end(JSON.stringify(err));
}
return res.end();
});
});
app.get('/load_images', function (req, res) {
FroalaEditor.Image.list('/uploads/', function (err, data) {
if (err) {
return res.status(404).end(JSON.stringify(err));
}
return res.send(data);
});
});
app.get('/get_amazon', function (req, res) {
var configs = {
bucket: process.env.AWS_BUCKET,
region: process.env.AWS_REGION,
keyStart: process.env.AWS_KEY_START,
acl: process.env.AWS_ACL,
accessKey: process.env.AWS_ACCESS_KEY,
secretKey: process.env.AWS_SECRET_ACCESS_KEY
};
var configsObj = FroalaEditor.S3.getHash(configs);
res.send(configsObj);
});
// Create folder for uploading files.
var filesDir = path.join(path.dirname(require.main.filename), 'uploads');
if (!fs.existsSync(filesDir)) {
fs.mkdirSync(filesDir);
}
// Health endpoint — reports live room/client counts from the relay.
app.get('/health', function (req, res) {
res.json(Collaborative.getStats());
});
// Wrap Express in a plain HTTP server so the WebSocket relay can share the port.
// Clients connect to: ws://localhost:3000/<docId>
var server = http.createServer(app);
Collaborative.attachToServer(server);
server.listen(3000, '0.0.0.0', function () {
console.log('Example app + collaborative relay listening on port 3000');
});