NoteDraw is a plugin for editing rendered note text and drawing directly on notes.
It is built as a surface layer: the same drawing and text-edit logic works on Obsidian reading view, source view, embedded note previews, and supported webview surfaces.
![[Markdown notes]] can be edited in place: direct editing in source view and the floating format toolbar in reading view.New drawing files are stored here:
<vault>/.obsidian/plugins/notedraw/drawings/
Each note gets a JSON file derived from its vault path. NoteDraw keeps drawing data separate from Markdown text, so Markdown sync and normal editing remain predictable.
Version 0.2.0 is the full NoteDraw rename and uses plugin id:
notedraw
If an older local prototype folder exists, NoteDraw can read its previous drawing JSON files and copy them into the new notedraw/drawings folder on first access. The old files are not deleted.
Copy these files into:
<vault>/.obsidian/plugins/notedraw/
Required files:
main.js
manifest.json
styles.css
Then enable:
Settings -> Community plugins -> Installed plugins -> NoteDraw
NoteDraw now keeps source code under src/ and builds the Obsidian runtime file at the repository root.
npm install
npm run build
Build output:
main.js
The release package still uses the standard Obsidian plugin layout:
main.js
manifest.json
styles.css
The source tree keeps extras/ for support-code images used at build time. Release builds embed those images into main.js, so the installed plugin does not require separate image files.
The settings page currently includes:
NoteDraw exposes a small API from the plugin instance:
const api = app.plugins.plugins.notedraw.api;
For convenience, it is also exposed while the plugin is loaded:
const api = window.NoteDraw;
Stable integration API (recommended for Cancip, scripts, and AI plugins):
const noteDraw = api.v1;
noteDraw.apiVersion; // "1.0"
noteDraw.capabilities;
noteDraw.listSurfaces();
await noteDraw.getState();
await noteDraw.activate({ tool: "edit-md" });
await noteDraw.toggle();
await noteDraw.setVisibility(true);
noteDraw.setTool("pen");
noteDraw.setTool({ tool: "draw", brush: "pen", variant: "fountain" });
noteDraw.setTool({ tool: "draw", brush: "watercolor", variant: "text-highlight" });
noteDraw.setBrush({ brush: "pen", variant: "fountain", color: "#e53935", width: 4, opacity: 0.9 });
noteDraw.setTextPreset("rectangle");
noteDraw.setZoom(1.25);
await noteDraw.selectElements({ ids: ["element-id"] });
await noteDraw.updateElements({ ids: ["element-id"], patch: { color: "#43a047", locked: true } });
await noteDraw.reorderElements({ ids: ["element-id"], direction: "front" });
await noteDraw.undo();
await noteDraw.readDrawings("Notes/example.md");
await noteDraw.writeDrawings("Notes/example.md", drawingData);
await noteDraw.replaceText({
path: "Notes/example.md",
originalText: "old rendered text",
editedText: "edited **Markdown** text"
});
await noteDraw.insertStroke("Notes/example.md", stroke);
const unsubscribe = noteDraw.on("markdown-changed", (event) => console.log(event));
await noteDraw.execute("set-tool", { tool: "select" });
Custom views from Cancip, NoteWeb, or another plugin can register their visible surface without copying NoteDraw internals:
const surface = noteDraw.registerSurface({
owner: "noteweb",
id: "tab:" + leaf.id,
host: webPageContainer,
source: {
kind: "url",
url: currentUrl,
title: documentTitle
},
capabilities: {
drawing: true,
textEditing: true,
elements: true,
attachments: true
},
viewport: {
getZoom: () => browserZoom,
setZoom: (zoom) => setBrowserZoom(zoom),
getScroll: () => ({ left: webPageContainer.scrollLeft, top: webPageContainer.scrollTop }),
onChange: (callback) => subscribeToViewport(callback)
}
});
await surface.ready;
await surface.activate("draw");
await surface.execute([
{ op: "set-brush", brush: "watercolor", variant: "text-highlight", color: "#facc15" },
{ op: "insert-elements", elements: generatedStrokes }
]);
surface.destroy();
Registering the same owner and id again updates its source on the same host. Vault paths, normalized URLs, and virtual keys receive separate persistent drawing storage. The returned handle exposes surface-scoped drawing, text, element, clipboard, history, mind-map, zoom, event, refresh, and lifecycle operations without exposing NoteDraw's internal controller.
The structured methods cover surface state, visibility, tools, brush settings, text presets, element selection and mutation, layers, locking, note flow, history, clipboard, settings, imports, exports, and lifecycle events. execute(action, options) accepts a string, one structured action object, or an ordered action array, so Cancip and other agents do not depend on internal controller method names.
Magic-wand actions are maintained on Markdown, Canvas, PDF, image, Base/database, HTML/webview, and other supported main-workspace pages. State-backed plugin pages use a stable NoteDraw storage identity even when they do not expose a Vault file.
Obsidian's built-in global and local graph views remain native interactive surfaces: NoteDraw does not automatically wrap their canvases or input handlers. Plugins can still opt a custom region into NoteDraw explicitly through api.v1.registerSurface().
The original top-level methods remain available for compatibility.
Example: read current note drawings.
const file = app.workspace.getActiveFile();
const drawings = await app.plugins.plugins.notedraw.api.v1.readDrawings(file);
console.log(drawings.strokes.length);
Example: insert a stroke.
const file = app.workspace.getActiveFile();
await app.plugins.plugins.notedraw.api.v1.insertStroke(file, {
brush: "pen",
color: "#e53935",
width: 3,
opacity: 1,
points: [
{ x: 0.2, y: 0.2 },
{ x: 0.5, y: 0.35 },
{ x: 0.7, y: 0.6 }
]
});
Example: AI-assisted text replacement.
const file = app.workspace.getActiveFile();
await app.plugins.plugins.notedraw.api.v1.replaceText({
file,
originalText: "old rendered text",
editedText: "edited Markdown text"
});
The API is intentionally plain JSON and string based so local AI agents can:
For safety, AI tools should read first, prepare a small patch, then write only the target note or drawing file.
NoteDraw is structured around controllers bound to visible note surfaces. That makes future support practical for:
The current package focuses on the local Obsidian plugin runtime. The API and DOM controller split are the extension points for broader web support.
Current version: 3.4.2.