Fabric.js Document Engine

Search documentation

Find a page or section

中文

Quick start

View as Markdown

Install the package, connect your existing canvas, and save it.

This guide shows how to save a Fabric.js canvas as JSON and load it back. You keep your own canvas, toolbar and UI. The engine only adds the document work around them.

Install

The package works with Fabric.js 6 and 7. Install it from npm next to fabric:

npm install fabricjs-document-engine fabric

Save and load

Connect the engine to the canvas you already have. toDocument() returns the whole canvas as plain JSON, and loadDocument() puts it back:

import { Canvas, Rect } from "fabric";
import { createDocumentEngine } from "fabricjs-document-engine";
 
const canvas = new Canvas("editor", { width: 800, height: 600 });
const engine = createDocumentEngine({ canvas });
 
canvas.add(new Rect({ width: 100, height: 80, fill: "tomato" }));
 
const document = engine.toDocument();
localStorage.setItem(document.id, JSON.stringify(document));
 
await engine.loadDocument(JSON.parse(localStorage.getItem(document.id)!));

Try it below. Add a few shapes, save a Fabric.js canvas as JSON with the button, move things around, then load from JSON to get the saved state back.

Loading the live demo. Its code is below.

save-load-demo.tsx
"use client";
 
import type { FabricDocument } from "fabricjs-document-engine";
import { useDocumentEngine } from "fabricjs-document-engine/react";
import { CircleIcon, FolderOpenIcon, SaveIcon, SquareIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
 
import {
  DemoCanvas,
  DemoFrame,
  newShape,
  seedShapes,
  Status,
  Toolbar,
  ToolButton,
} from "./demo-ui";
import { useFabricCanvas } from "./use-fabric-canvas";
 
export default function SaveLoadDemo() {
  const t = useTranslations("demos");
  const { elementRef, canvas } = useFabricCanvas();
  const engine = useDocumentEngine(canvas);
  const [saved, setSaved] = useState<FabricDocument>();
 
  useEffect(() => {
    if (!engine) return;
    engine.canvas.add(...seedShapes());
    engine.clearHistory();
  }, [engine]);
 
  function save() {
    // The whole canvas as plain JSON. Store it anywhere.
    setSaved(engine?.toDocument());
  }
 
  async function load() {
    if (saved) await engine?.loadDocument(saved);
  }
 
  return (
    <DemoFrame>
      <DemoCanvas elementRef={elementRef} label={t("canvas")} />
 
      <Toolbar>
        <ToolButton
          icon={SquareIcon}
          label={t("square")}
          onClick={() => canvas?.add(newShape("square"))}
        />
        <ToolButton
          icon={CircleIcon}
          label={t("circle")}
          onClick={() => canvas?.add(newShape("circle"))}
        />
        <span className="ml-auto flex gap-1.5">
          <ToolButton
            icon={SaveIcon}
            label={t("saveLoad.save")}
            primary
            disabled={!engine}
            onClick={save}
          />
          <ToolButton
            icon={FolderOpenIcon}
            label={t("saveLoad.load")}
            disabled={!saved}
            onClick={() => void load()}
          />
        </span>
      </Toolbar>
 
      <Status tone={saved ? "ok" : "idle"}>
        {saved
          ? t("saveLoad.saved", { count: saved.objects.length })
          : t("saveLoad.hint")}
      </Status>
 
      <pre className="h-32 overflow-auto rounded-lg border bg-muted p-3 font-mono text-xs">
        {saved ? JSON.stringify(saved, null, 2) : "{}"}
      </pre>
    </DemoFrame>
  );
}

Every object in the saved JSON has a stable id. It survives moving, grouping, saving and reopening, so you can link objects to your own data.

What you get for free

Creating the engine also turns on undo redo. Every user action, such as adding, moving or resizing a shape, becomes one undo step:

await engine.undo();
await engine.redo();

Save to storage

For real apps, give the engine a storage adapter. Then save() and load(id) work, and autosave keeps the document saved while the user edits:

import { createDocumentEngine } from "fabricjs-document-engine";
import { createLocalStorage } from "fabricjs-document-engine/storage";
 
const engine = createDocumentEngine({
  canvas,
  storage: createLocalStorage({ prefix: "my-app:" }),
  autosave: true,
});
 
await engine.load("project-42");
await engine.save();

The same code works when you save a Fabric.js canvas as JSON to your own database. See storage adapters.

Next steps