Open Source · MIT · Zero deps

Build interactive
mind maps
on the web

Drop in one JS file. Full CRUD API, SVG rendering, drag & drop, pan, zoom, and lossless PNG/SVG export — no build step required.

Live Demo GitHub npm
npm version license no dependencies bundle size

Live Demo

Click any node to select it. Use the panel below to add children, rename, or delete. Scroll to zoom, drag background to pan.

No node selected
Click any node in the map above to see actions.

Everything you need

A complete mind map solution in a single file. No bundler, no framework, no dependencies.

🖼️

SVG Rendering

Pure SVG — crisp at any resolution, no canvas artifacts. Drop-shadow filters, colour-coded branches, smooth bezier links.

🖱️

Interactive Canvas

Drag individual nodes. Pan by dragging the background. Scroll-wheel zoom centred on the cursor. Pinch-to-zoom on touch.

✏️

Full CRUD API

Add, update, delete nodes programmatically. Insert a node between any two connected nodes without breaking the tree.

📤

Export PNG & SVG

One-line export to high-DPI PNG or vector SVG. The export is pixel-perfect — no dependencies, pure web platform APIs.

🔄

JSON Import / Export

Load any tree as JSON with fromJSON(). Snapshot the current state with toJSON() and save or share it.

📡

Event System

Subscribe to nodeClick, nodeAdd, nodeUpdate, and nodeDelete to build reactive UIs on top.

Install

Two ways to add mindkeeper-map.js to your project.

1
CDN (no build step)
<!-- Drop this in your HTML -->
<script src="https://cdn.jsdelivr.net/npm/mindkeeper-map/src/mindkeeper-map.js"></script>

<!-- Create a container -->
<div id="map" style="width:100%;height:500px"></div>

<!-- Initialise -->
<script>
const map = new MindkeeperMap('#map');
map.init({ topic: 'Root', children: [] });
</script>
2
npm
# Install
npm install mindkeeper-map

// CommonJS
const MindkeeperMap = require('mindkeeper-map');

// Or copy the file directly from
// node_modules/mindkeeper-map/src/mindkeeper-map.js

// Initialise
const map = new MindkeeperMap('#map');
map.init(treeData);

API Reference

All methods return this unless noted, so they chain.

new MindkeeperMap(selector, options?)

Create an instance. selector is a CSS selector string or an Element. All options are optional:

{
  levelWidth: 240,   // px between depth levels
  vSpacing:   54,    // px between siblings
  linkStroke: 3,     // link line width
  nodeBorder: 2.5,   // node border width
  fontFamily: 'system-ui'
}
.init(data) → this

Render the map from a tree object. Ids are optional — any node without one gets a stable auto-generated id.

map.init({
  id: 'root',
  topic: 'My Mind',
  children: [
    { topic: 'Work', children: [
      { topic: 'Q3 launch' }
    ]},
    { topic: 'Personal' }
  ]
});
.addNode(parentId, { topic }) → string

Add a child node to the given parent. Returns the new node's id.

const id = map.addNode('root', {
  topic: 'New branch'
});
.insertBetween(parentId, childId, { topic }) → string

Insert a new node between a parent and one of its children. The existing child becomes a child of the new node.

// Before: Root → Work
const id = map.insertBetween(
  'root', 'work',
  { topic: 'Category' }
);
// After:  Root → Category → Work
.updateNode(id, { topic }) → this

Update a node's label. The map re-renders automatically.

map.updateNode(id, { topic: 'New label' });
.deleteNode(id) → this

Delete a node and all its descendants. Cannot delete the root node.

map.deleteNode(id);
.getNode(id) → object | null

Returns lightweight node info: { id, topic, children: string[] }.

const node = map.getNode('work');
// { id: 'work', topic: 'Work',
//   children: ['q3', 'team'] }
.fromJSON(data) / .toJSON() → object

fromJSON is an alias for init. toJSON returns the current tree as a plain JS object you can serialise.

const json = map.toJSON();
localStorage.setItem('map', JSON.stringify(json));

map.fromJSON(JSON.parse(saved));
.exportPNG(filename?) / .exportSVG(filename?)

Download the full mindmap as a high-DPI PNG or a clean vector SVG. No dependencies — built on XMLSerializer and Canvas 2D.

map.exportPNG('my-map.png');
map.exportSVG('my-map.svg');
.on(event, handler) / .off(event, handler)

Subscribe and unsubscribe from events. Returns this for chaining.

map
  .on('nodeClick', ({ id, topic }) => {
    console.log('Clicked:', topic);
  })
  .on('nodeAdd', ({ node, parentId }) => {
    save(map.toJSON());
  });

Events

Event Payload Description
nodeClick { id, topic } A node was clicked
nodeAdd { node: { id, topic }, parentId } A node was added via addNode or insertBetween
nodeUpdate { id, data } A node was updated via updateNode
nodeDelete { id } A node was deleted via deleteNode
nodeDeselect {} The background was clicked, clearing selection

JSON Format

The input and output format is a recursive tree. The id field is optional on input — the library generates stable ids for any node that omits it.

{
  "id": "root",
  "topic": "Project Plan",
  "children": [
    {
      "id": "design",
      "topic": "Design",
      "children": [
        { "topic": "Wireframes" },
        { "topic": "Prototype" }
      ]
    },
    {
      "id": "dev",
      "topic": "Development",
      "children": [
        { "topic": "API" },
        { "topic": "Frontend" }
      ]
    }
  ]
}
💡

Works with mindkeeper-mcp

Export your mind map from the mindkeeper-mcp CLI or MCP server (~/.mindkeeper/mindmap.json) and pass it directly to fromJSON().

💾

Persist to localStorage

Call toJSON() after any mutation to snapshot the tree and save it wherever you need — localStorage, a database, or a file.

map.on('nodeAdd', () =>
  localStorage.setItem(
    'map',
    JSON.stringify(map.toJSON())
  )
);