Get started
Install the package and add the editor to a component.
- React
- Next.js
- Other frameworks
- Install the package:
npm install @drawio/react
- Add the editor to a component. It fills its container, so give the container a height:
import { useState } from "react";
import { DrawioEditor } from "@drawio/react";
// The editor loads `xml` and calls onSave when the user clicks Save.
export default function QuickStart()
{
const [xml, setXml] = useState<string>();
return (
<div style={{ height: 600 }}>
<DrawioEditor
xml={xml}
onSave={(event) => setXml(event.xml)}
// Light or dark like the system
urlParameters={{ dark: "auto" }}
/>
</div>
);
}
The draw.io editor will open in your app. When the user clicks Save, onSave receives the diagram as XML. Continue with the examples for autosave, exports and more.
- Install the package:
npm install @drawio/react
- Add the editor to a client component:
"use client";
import { DrawioEditor } from "@drawio/react";
export default function Diagram({ xml }: { xml?: string })
{
return (
<div style={{ height: "80vh" }}>
<DrawioEditor xml={xml} onSave={(event) => console.log(event.xml)} />
</div>
);
}
- Use the component in a page:
import Diagram from "./Diagram";
export default function Page()
{
return <Diagram />;
}
The page renders on the server, and the editor starts in the browser.
For Vue, Angular, Svelte or plain TypeScript, use DrawioClient with an iframe of your own. It has the same methods and events as the React component.
import { DrawioClient, buildEditorUrl } from "@drawio/react";
const iframe = document.querySelector("iframe")!;
const client = new DrawioClient(iframe, {
url: buildEditorUrl(),
onInit: (client) => client.load({ xml: "" }),
onEvent: (event) =>
{
if (event.event == "save")
{
console.log(event.xml);
}
}
});
const png = await client.export({ format: "png" });
Examples
Each example is one file. Copy it into your app, or run all of them from the repository.
- Autosave
- Export
- Editable image
- Inline editing
- Mermaid
- Templates
- Options
Stores every change, here in the browser's local storage. setModified(false) tells the editor that the change is stored, so it doesn't ask before the page is left.
import { useRef, useState } from "react";
import { DrawioEditor, type DrawioEditorHandle } from "@drawio/react";
const KEY = "drawio-example";
// Saves every change in the browser's local storage. Reload the page: the
// diagram is still there. Replace localStorage with a call to your backend.
export default function Autosave()
{
const [xml] = useState(() => localStorage.getItem(KEY) ?? undefined);
const editor = useRef<DrawioEditorHandle>(null);
return (
<div style={{ height: 600 }}>
<DrawioEditor
ref={editor}
xml={xml}
autosave
onAutosave={(event) =>
{
localStorage.setItem(KEY, event.xml!);
// Stored: the editor does not ask before the page is left
editor.current!.setModified(false);
}}
// Light or dark like the system, without the Save, Save and Exit and Exit buttons
urlParameters={{ dark: "auto", noSaveBtn: true, saveAndExit: false, noExitBtn: true }}
/>
</div>
);
}
Your own buttons call the editor through a ref. export() returns PNG, SVG or an editable SVG as a data URI, and getXml() the .drawio file.
import { useRef } from "react";
import { DrawioEditor, type DrawioEditorHandle } from "@drawio/react";
// Your own buttons call the editor through a ref.
export default function Export()
{
const editor = useRef<DrawioEditorHandle>(null);
async function download(format: "png" | "svg" | "xmlsvg")
{
const result = await editor.current!.export({ format });
const link = document.createElement("a");
link.href = result.data as string;
link.download = (format == "xmlsvg") ? "diagram.drawio.svg" : "diagram." + format;
link.click();
}
async function downloadXml()
{
const xml = await editor.current!.getXml();
const link = document.createElement("a");
link.href = "data:application/xml;charset=utf-8," + encodeURIComponent(xml);
link.download = "diagram.drawio";
link.click();
}
return (
<>
<div className="toolbar">
<button onClick={() => download("png")}>PNG</button>
<button onClick={() => download("svg")}>SVG</button>
<button onClick={() => download("xmlsvg")}>Editable SVG</button>
<button onClick={downloadXml}>.drawio</button>
</div>
<div style={{ height: 600 }}>
<DrawioEditor ref={editor} urlParameters={{ dark: "auto", noSaveBtn: true, saveAndExit: false, noExitBtn: true }} />
</div>
</>
);
}
Stores the diagram as an SVG image with the diagram inside, a .drawio.svg file. Any page can show the image, and the editor opens it again. This is how draw.io for Notion stores diagrams.
import { useRef, useState } from "react";
import { DrawioEditor, type DrawioEditorHandle } from "@drawio/react";
// Stores the diagram as an SVG image with the diagram inside (.drawio.svg),
// like draw.io for Notion. Any page can show the image, and the editor can
// open it again. Click the image to edit it.
export default function EditableImage()
{
const [image, setImage] = useState<string>();
const [editing, setEditing] = useState(false);
const editor = useRef<DrawioEditorHandle>(null);
async function save(exit?: boolean)
{
const result = await editor.current!.export({ format: "xmlsvg" });
setImage(result.data as string);
if (exit)
{
setEditing(false);
}
}
return (
<>
{(image != null) ?
<img src={image} alt="Diagram" className="diagram" title="Click to edit"
onClick={() => setEditing(true)} /> :
<button onClick={() => setEditing(true)}>Insert a diagram</button>}
{editing &&
<div className="overlay">
<DrawioEditor
ref={editor}
xml={image}
// Light or dark like the system, with a Save and Exit and an Exit button
urlParameters={{ dark: "auto", noSaveBtn: true, saveAndExit: true }}
onSave={(event) => save(event.exit)}
onExit={() => setEditing(false)}
/>
</div>}
</>
);
}
Edits the image in place, like draw.io for Notion: the editor opens over the image. Press Escape to stop editing, and the image shows the changes.
import { useRef, useState } from "react";
import { DrawioEditor, type DrawioEditorHandle, type DrawioEvent } from "@drawio/react";
// Edits a diagram image in place, like draw.io for Notion: the editor opens
// over the image (embedInline). The iframe covers the window and stays
// hidden until an image is edited. Press Escape to stop editing.
export default function InlineEditing()
{
const [image, setImage] = useState<string>();
const [editing, setEditing] = useState(false);
const editor = useRef<DrawioEditorHandle>(null);
const target = useRef<HTMLDivElement>(null);
function edit()
{
const r = target.current!.getBoundingClientRect();
const border = 8;
setEditing(true);
editor.current!.load({
xml: image,
border,
rect: { left: r.left - border, top: r.top - border, width: r.width + 2 * border, height: r.height + 2 * border },
minWidth: Math.min(600, innerWidth - 60),
minHeight: Math.min(400, innerHeight - 60),
maxFitScale: 1.5,
viewport: { x: 0, y: 0, width: innerWidth, height: innerHeight },
// The image shows the diagram in the light or dark mode of the page
theme: "auto"
});
}
function handleEvent(event: DrawioEvent)
{
if (event.event == "export" && event.exit)
{
// The edited diagram as an SVG with the diagram inside
setImage(event.data as string);
setEditing(false);
}
else if (event.event == "exit")
{
setEditing(false);
}
else if (event.event == "resize")
{
// The editor asks to go fullscreen or back
editor.current!.fullscreenChanged(event.fullscreen);
}
}
return (
<>
<div ref={target} className="inline-target" onClick={edit} title="Click to edit">
{(image != null) ? <img src={image} alt="Diagram" /> : <span>Click to draw a diagram</span>}
</div>
<DrawioEditor
ref={editor}
urlParameters={{ embedInline: true, dark: "auto" }}
onEvent={handleEvent}
style={{
position: "fixed", inset: 0, zIndex: 10, background: "transparent",
visibility: editing ? "visible" : "hidden"
}}
/>
</>
);
}
Converts Mermaid to an editable diagram. Use {format: "csv", data} for CSV and {format: "plantuml", data} for PlantUML.
import { useState } from "react";
import { DrawioEditor, type Descriptor } from "@drawio/react";
const SOURCE = `graph TD
A[Start] --> B{Ready?}
B -->|Yes| C[Ship it]
B -->|No| D[Fix it]
D --> B`;
// Converts Mermaid to an editable diagram. Use {format: "csv", data} for CSV.
export default function Mermaid()
{
const [source, setSource] = useState(SOURCE);
const [descriptor, setDescriptor] = useState<Descriptor>({ format: "mermaid", data: SOURCE });
return (
<div className="split">
<div className="side">
<textarea value={source} onChange={(e) => setSource(e.target.value)} spellCheck={false} />
<button onClick={() => setDescriptor({ format: "mermaid", data: source })}>Convert</button>
</div>
<DrawioEditor descriptor={descriptor} urlParameters={{ dark: "auto" }} />
</div>
);
}
Starts a new diagram with the template dialog. The selected template is loaded with autosave on.
import { DrawioEditor } from "@drawio/react";
// A new diagram starts with the template dialog. The selected template is
// loaded with autosave on.
export default function Templates()
{
return (
<div style={{ height: 600 }}>
<DrawioEditor
templates
autosave
urlParameters={{ dark: "auto" }}
onAutosave={(event) => console.log("Diagram changed", event.xml?.length)}
/>
</div>
);
}
Changes the theme, dark mode, language and shape libraries. The configuration sets fonts, colours, default styles and much more.
import { useState } from "react";
import { DrawioEditor, type UrlParameters } from "@drawio/react";
// URL parameters change the look of the editor. A change reloads it with
// the diagram. The configuration customises fonts, colours, styles and
// shape libraries:
// https://www.drawio.com/docs/reference/configure-diagram-editor
export default function Options()
{
const [ui, setUi] = useState<UrlParameters["ui"]>("kennedy");
const [dark, setDark] = useState<UrlParameters["dark"]>("auto");
const [lang, setLang] = useState("en");
return (
<>
<div className="toolbar">
<select value={ui} onChange={(e) => setUi(e.target.value as UrlParameters["ui"])}>
<option value="kennedy">Default</option>
<option value="min">Minimal</option>
<option value="atlas">Atlas</option>
<option value="sketch">Sketch</option>
<option value="simple">Simple</option>
</select>
<select value={String(dark)} onChange={(e) => setDark((e.target.value == "auto") ? "auto" : e.target.value == "true")}>
<option value="auto">Light or dark like the system</option>
<option value="false">Light</option>
<option value="true">Dark</option>
</select>
<select value={lang} onChange={(e) => setLang(e.target.value)}>
<option value="en">English</option>
<option value="de">Deutsch</option>
<option value="fr">Français</option>
<option value="ja">日本語</option>
</select>
</div>
<div style={{ height: 600 }}>
<DrawioEditor
urlParameters={{ ui, dark, lang, libraries: true }}
config={{
defaultLibraries: "general;flowchart",
presetColors: ["E6F4EA", "FCE8E6", "E8F0FE", "FEF7E0"],
defaultVertexStyle: { rounded: "1" }
}}
/>
</div>
</>
);
}
Run all examples on your computer:
git clone https://github.com/jgraph/drawio-react.git
cd drawio-react
npm install
npm run examples
What the component does
It runs the draw.io editor in an iframe in embed mode, and handles the messages between your app and the editor.
Running in minutes
One component with props for the diagram and callbacks for save, autosave and exit. The editor fills its container.
Typed protocol
Every action and event of embed mode has a TypeScript type. Methods such as export() and getXml() return promises, and wait for the editor to load.
Safe messages
Accepts messages only from its own iframe and the origin of the editor, and sends diagrams only there. Several editors on one page work independently.
Next.js and SSR
Marked as a client component. On the server, it renders the iframe without starting the editor, so no message is lost during hydration.
Self-hosted draw.io
Set baseUrl to load the editor from your own server, for example from the jgraph/drawio Docker image.
jgraph/drawio on Docker Hub →GitHub repository
The source code, the examples to run, and the tests against the draw.io editor.
jgraph/drawio-react on GitHub →Props and methods
The most used ones. The types in the package describe all of them.
| Prop | Description |
|---|---|
xml | The diagram: draw.io XML, or a PNG or SVG data URI with a diagram inside. A new value loads it, unless it is the diagram the editor last sent. |
descriptor | Mermaid, PlantUML or CSV to convert instead: {format: "mermaid", data} |
autosave | Calls onAutosave after every change |
templates | Shows the template dialog when there is no xml |
urlParameters | Look of the editor, for example {ui: "min", dark: "auto", lang: "de", libraries: true}. A change reloads the editor with the current diagram. See the URL parameters. |
config | The configuration: fonts, colours, styles and shape libraries |
loadOptions | More options for loading, for example title, dark, fit or exportProtocol. See the load action. |
baseUrl | URL of a self-hosted draw.io. The default is https://embed.diagrams.net/. |
onLoad, onSave, onAutosave, onExit, onExport | Events of the editor |
onEvent | Every event, with its type: switch (event.event) |
| Method of the ref | Description |
|---|---|
getXml() | Returns the diagram |
export({format}) | Returns a PNG, SVG, editable SVG (xmlsvg), JSON or HTML export |
load({xml}), merge(xml) | Replaces the diagram, or merges changes into it |
setModified(false) | Marks the diagram as stored, for example after an autosave |
fit(), viewbox(rect), layout(layouts) | Changes the view, or arranges the diagram |
prompt(), dialog(), status(), spinner() | Shows a prompt, a message, a status or a spinner in the editor |
invokeAction(name) | Runs an action of the editor, for example undo, zoomIn or save |
Privacy and security
- Your app stores the diagrams. The editor sends them to your page, and draw.io doesn't store them.
- Set
baseUrlto a fixed address, never to user input: the editor at that address gets your diagrams. - The editor loads from embed.diagrams.net. To load it from your own server, run draw.io yourself and set
baseUrl. - The component accepts messages only from its own iframe and the origin of the editor, and sends diagrams only to that origin.
- Links in a diagram come from the diagram. Check the
hrefofopenLinkevents before your app opens them.
Read more about security and privacy in draw.io.
Questions and troubleshooting
The editor shows a 403 error
embed.diagrams.net serves pages on the standard ports of http and https, and on localhost on any port. A page on another port, a headless browser and some sites get a 403 error in the frame. For those, run draw.io yourself, for example with the jgraph/drawio Docker image, and set baseUrl to its address, here http://localhost:8080/:
docker run -it --rm -p 8080:8080 jgraph/drawio
The editor shows the spinner and doesn't load the diagram
Check the console of your browser. If the URL of the editor redirects, for example from http to https, the component ignores the messages of the new address and logs a warning. Use the final URL as baseUrl.
If your app has a Content Security Policy, allow the editor in a frame:
frame-src https://embed.diagrams.net
How do I store diagrams in my backend?
Send the XML of onSave or onAutosave to your server. After an autosave is stored, call setModified(false):
<DrawioEditor
ref={editor}
xml={xml}
autosave
onAutosave={async (event) =>
{
await fetch("/api/diagrams/1", { method: "PUT", body: event.xml });
editor.current!.setModified(false);
}}
/>
How do I show a diagram without the editor?
Store it as an SVG image with the diagram inside, with export({format: "xmlsvg"}), and show it with an img element. The editor opens the image again. See the editable image example.
Which files can the editor open?
The xml prop accepts draw.io XML, PNG and SVG files with a diagram inside as data URIs, Visio files as data URIs, and Lucidchart and Gliffy JSON. The descriptor prop converts Mermaid, PlantUML and CSV.
Can I hide buttons and menus?
Yes. urlParameters={{noSaveBtn: true, saveAndExit: false, noExitBtn: true}} hides the Save, Save and Exit and Exit buttons. The hideMenuItems option of the configuration hides menu items.
How do I use dark mode?
Set urlParameters={{dark: "auto"}}, and the editor follows the light or dark mode of the system. Use true or false to set it. With "auto", the SVG images of export({format: "xmlsvg"}) also show the diagram in the light or dark mode of the page.
Can I put several editors on one page?
Yes. Each editor accepts only the messages of its own iframe.
Do I need an API key or an account?
No. The component and the editor are free to use, and the editor needs no sign-in.
Versions and changes
Learn more
The embed mode reference describes every message between your app and the editor, and the configuration reference every option of the editor.