# Editor tutorial

> A Fabric.js editor tutorial in plain TypeScript: build a canvas editor with shapes, undo redo, autosave to localStorage and export to PNG.

Source: https://fabricjs-document-engine.jscrate.dev/docs/overview/tutorial
Last updated: 2026-09-28

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](https://fabricjs-document-engine.jscrate.dev/docs/frameworks/react) uses the same engine calls.

## 1. The page

Start with a canvas and a few buttons:

```html
<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:

```ts
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:

```ts
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:

```ts
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:

```ts
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:

```ts
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](https://fabricjs-document-engine.jscrate.dev/docs/guides/save-conflicts): what happens when two tabs edit one document.
- [Recovery](https://fabricjs-document-engine.jscrate.dev/docs/guides/recovery): keep unsaved work after a crash.
- [Custom backend](https://fabricjs-document-engine.jscrate.dev/docs/storage/custom-backend): save to your own API instead of localStorage.
