In this Fabric.js editor tutorial you build a small canvas editor with plain TypeScript. It adds shapes, supports undo redo, saves on its own and exports a PNG. Each step adds one feature, so you can stop at any point and still have working code.
If you use React, read this first anyway. The React guide uses the same engine calls.
1. The page
Start with a canvas and a few buttons:
<canvas id="editor"></canvas>
<div class="toolbar">
<button id="add">Add square</button>
<button id="undo">Undo</button>
<button id="redo">Redo</button>
<button id="export">Export PNG</button>
<span id="status" aria-live="polite"></span>
</div>2. The canvas and the engine
Create the Fabric.js canvas as usual, then connect the engine to it:
import { Canvas, Rect } from "fabric";
import { createDocumentEngine } from "fabricjs-document-engine";
import { createLocalStorage } from "fabricjs-document-engine/storage";
const canvas = new Canvas("editor", {
width: 800,
height: 500,
backgroundColor: "#ffffff",
});
const engine = createDocumentEngine({
canvas,
storage: createLocalStorage({ prefix: "tutorial:" }),
autosave: { delay: 1000 },
document: { id: "my-drawing" },
});storage tells the engine where documents live. autosave saves one second after the user stops editing.
3. Add shapes
Fabric.js fires an event when an object is added, so the engine records it as an undo step on its own:
document.querySelector("#add")!.addEventListener("click", () => {
canvas.add(
new Rect({
left: 50 + Math.random() * 600,
top: 50 + Math.random() * 350,
width: 80,
height: 60,
fill: "#e76f51",
})
);
});4. Undo and redo
Wire the buttons to undo() and redo(), and keep them in sync with the history:change event:
import { bindKeyboardShortcuts } from "fabricjs-document-engine";
const undoButton = document.querySelector<HTMLButtonElement>("#undo")!;
const redoButton = document.querySelector<HTMLButtonElement>("#redo")!;
undoButton.addEventListener("click", () => engine.undo());
redoButton.addEventListener("click", () => engine.redo());
engine.on("history:change", ({ canUndo, canRedo, undoLabel }) => {
undoButton.disabled = !canUndo;
redoButton.disabled = !canRedo;
undoButton.textContent = undoLabel ? `Undo ${undoLabel}` : "Undo";
});
bindKeyboardShortcuts(engine);Now Ctrl+Z and Cmd+Z undo, and Ctrl+Shift+Z redoes. Moving or resizing a shape with the mouse is one undo step too.
5. Autosave and the save status
Autosave already runs. Show its state, and open the saved drawing when the page loads:
const status = document.querySelector("#status")!;
engine.on("save:status", ({ status: state }) => {
status.textContent = state === "saved" ? "All changes saved" : state;
});
await engine.load("my-drawing").catch(() => {
// Nothing saved yet: start with the empty document.
});Refresh the page. Your shapes are still there.
6. Export a PNG
export() checks images and fonts first, then renders. downloadExport() starts the download:
import { downloadExport } from "fabricjs-document-engine";
document.querySelector("#export")!.addEventListener("click", async () => {
const result = await engine.export({ format: "png", scale: 2 });
downloadExport(result, "drawing.png");
});What you built
In about 60 lines, the canvas editor from this Fabric.js editor tutorial can:
- add shapes and undo or redo every action,
- autosave to localStorage and reopen after a refresh,
- export PNG at twice the size.
Next steps
- Save conflicts: what happens when two tabs edit one document.
- Recovery: keep unsaved work after a crash.
- Custom backend: save to your own API instead of localStorage.