FB Mind Map
Create, edit and organise interactive mind maps inside the WordPress admin. Open source, no build step, no external services, no API keys, no tracking.
by Fatih Bora — FB Software Solutions · github.com/fatihborasoftware-sudo/fb-mind-map · website
Install
The author publishes release zips, so WP-CLI can install straight from GitHub:
wp plugin install https://github.com/fatihborasoftware-sudo/fb-mind-map/releases/download/v0.3.0/fb-mind-map-0.3.0.zipReadme
FB Mind Map
Create, edit and organise interactive mind maps inside the WordPress admin.
No external services, no API keys, no tracking, no build step. Download the plugin, upload it, and it works.
What 0.3.0 does
- A Mind Maps menu in wp-admin with a proper WordPress list table — search, sorting, pagination, node counts, duplicate and trash.
- Create a map from a one-field form: a single root node, a blank canvas, or an example structure to poke at.
- A full-screen editor inside wp-admin: toolbar, canvas, node inspector, navigator and status bar.
- Nodes — root, child and sibling; rename inline; delete a branch; drag a node and its whole subtree; collapse and expand with a visible child count.
- Automatic layout with branches balanced left and right of the root, per-branch colours inherited by descendants, and curved connectors.
- Pan and zoom, fit-to-screen, and a navigator that frames your content rather than an arbitrary giant canvas.
- Undo and redo, and copy / cut / paste of whole branches.
- Save and reload, with autosave on by default and a warning if you try to leave with unsaved work.
- Movable panels — drag the inspector or the navigator anywhere by its header, minimise either to its title bar. Where you put them is remembered for you alone.
- Double-click empty canvas to add a child of the selected node, dropped exactly where you clicked and already in rename mode.
- Notes, made of blocks: text, HTML with a sandboxed live preview, an AI prompt stored for later, images or files from your Media library, and a video from YouTube or Vimeo. A small ✎ on the node opens a peek; Open takes you to a full-screen workspace with a sidebar of every note in the map, search and an outline.
- The video comes up with the note, in its own window placed on whichever side of the note has room — right, left, below or above — joined to it by a short tether. Drag it by its header, resize it with − and +, push it to full screen, or close it with Esc. The note stays open behind it, so you can read and watch at once.
- English and Türkçe, with a finished Turkish translation in the box and a switcher in the toolbar for people who want the plugin in a different language from the rest of the site.
- The admin menu folds while a map is open, and unfolds everywhere else.
- An example map you cannot delete, created once on activation. Its content is how this plugin was actually built — seven stages in the order they happened, mistakes included — and every stage's note carries the prompt that produces it. It is made out of the plugin's own block types, so opening it is also a tour of notes, the workspace and the video box. Edit it freely; Reset on its row puts it back.
- An author credit at the foot of the plugin's own screens — three styles or off — plus Settings / YouTube / GitHub / Support on its Plugins row, and a welcome panel on an empty map list. Nothing on the Dashboard, no banners, no review nag.
What 0.3.0 deliberately does not do
Node links, icons, drawing, cross-branch connectors, free-floating nodes, templates, import/export, AI generation (prompts are stored, never run), front-end display, real-time collaboration. The inspector shows greyed-out rows for links and icons so the shape is visible — nothing behind them is built yet.
Install
From a zip
- Plugins → Add New → Upload Plugin
- Choose
fb-mind-map.zip - Install, then Activate
- A Mind Maps item appears in the admin menu
From git
cd wp-content/plugins
git clone https://github.com/fatihborasoftware-sudo/fb-mind-map.git
Then activate it from the Plugins screen. There is nothing to build and nothing to install — no npm, no Composer.
How it is put together
fb-mind-map/
├── fb-mind-map.php bootstrap: constants, includes, hooks
├── uninstall.php opt-in data removal on delete
├── includes/
│ ├── class-fb-mind-map-capabilities.php who may do what
│ ├── class-fb-mind-map-demo.php the example map and its protection
│ ├── class-fb-mind-map-post-type.php the fb_mind_map CPT
│ ├── class-fb-mind-map-data.php the map JSON: defaults + sanitising
│ └── class-fb-mind-map-rest.php GET and POST /maps/<id>
├── admin/
│ ├── class-fb-mind-map-admin.php menus, screens, asset loading
│ ├── class-fb-mind-map-list-table.php the All Mind Maps table
│ └── views/
│ ├── list.php
│ ├── new.php
│ └── editor.php
├── assets/
│ ├── css/
│ │ ├── fb-mind-map-admin.css
│ │ └── fb-mind-map-editor.css
│ └── js/
│ ├── fb-mind-map-engine.js the canvas: layout, drawing, interaction
│ ├── fb-mind-map-notes.js the note popover and note workspace
│ └── fb-mind-map-editor.js the wp-admin glue: toolbar, panels, saving
└── languages/
├── fb-mind-map.pot
├── fb-mind-map-tr_TR.po
└── fb-mind-map-tr_TR.mo
includes/ also holds class-fb-mind-map-settings.php (site options and the
per-user editor layout) and class-fb-mind-map-i18n.php (the language
override), and admin/views/settings.php is the settings screen.
The three JavaScript files, and why they are separate
fb-mind-map-engine.js knows how to draw and edit a mind map. It knows
nothing about WordPress — no REST, no nonces, no admin markup. You can drop it
into a plain HTML page and work on the canvas on its own.
fb-mind-map-notes.js is the note popover and the full-screen note workspace.
It owns the block editor and the media library integration.
fb-mind-map-editor.js is the only file that talks to WordPress. It starts the
engine, wires the toolbar, manages the floating panels, draws the inspector and
the right-click menu, saves over REST, and stores the language choice.
That line exists so the drawing half can be replaced — with a third-party library, or with a rewrite — without touching the WordPress half.
Where a map is stored
One mind map is one fb_mind_map post. The post title is the map's name. The
structure lives in post meta under _fb_mind_map_data as a JSON string:
{
"version": 2,
"root": {
"id": "n1",
"text": "Central idea",
"side": "",
"color": "",
"collapsed": false,
"dx": 0,
"dy": 0,
"blocks": [],
"children": []
}
}
A note is the blocks array on a node:
{ "type": "text", "text": "plain text, newlines kept" }
{ "type": "html", "text": "<p>markup, wp_kses_post on save</p>" }
{ "type": "prompt", "text": "stored, never executed" }
{ "type": "image", "attachment": 42, "caption": "…" }
{ "type": "file", "attachment": 43, "caption": "…" }
{ "type": "video", "url": "https://www.youtube.com/watch?v=…", "caption": "…" }
Media is stored as an attachment ID, never a URL. The filename, size and URL are looked up from the media library each time the editor loads, so replacing a file in Media updates every note that references it.
side and color are only meaningful on first-level branches. dx / dy
are the manual offset from the automatic layout — which is what lets "Auto
layout" put everything back.
One JSON document rather than a table of node rows, because a mind map is always read and written whole. There is no query like "find every node named X" that would justify the extra tables, and one document keeps saving atomic.
The REST API
GET /wp-json/fb-mind-map/v1/maps/<id> read one map
POST /wp-json/fb-mind-map/v1/maps/<id> save one map
POST /wp-json/fb-mind-map/v1/prefs/ui remember this user's panel layout
POST /wp-json/fb-mind-map/v1/prefs/locale remember this user's language
The map routes require edit_fb_mind_map on that specific map; the preference
routes require edit_fb_mind_maps. All of them require a valid X-WP-Nonce
header.
POST /maps/<id> body:
{ "title": "My map", "data": { "version": 2, "root": { … } } }
Security notes
Worth reading if you are learning from this plugin, because these are the places WordPress plugins usually get it wrong.
- Node text is plain text, always. It is sanitised with
sanitize_text_field()on the way in and written to the DOM withtextContenton the way out — neverinnerHTML. Several mind map libraries offer an "allow HTML in nodes" option; that option is a stored-XSS hole. - Every REST route has a real
permission_callbackthat checks the current user against this map. Apermission_callbackthat returnstrueis the most common security bug in the plugin directory. - Every form and link that changes something carries a nonce, checked with
check_admin_referer(). - Custom capabilities, not
manage_options. Mind maps can be given to an editor or an author without handing over the whole site. - HTML lives in exactly one place. Only an HTML block may contain markup,
it goes through
wp_kses_post()on save, and the editor previews it inside aniframewith an emptysandboxattribute — no scripts, no same-origin access. A note is written by one person and read by another; rendering its markup into the admin page would hand the author a script that runs as whoever opens the map next. - Uploads go through the WordPress media library, so they inherit its
permissions and file-type rules, appear in Media like anything else, and are
gated on
upload_files. The attachment ID is re-checked on save: a block pointing at something that is not an attachment is dropped. - The document is bounded: at most 2000 nodes, 40 levels deep, 500 characters of text per node, 40 blocks per note, 20,000 characters per block. The sanitiser rebuilds every node field by field, drops unknown keys, rejects duplicate ids, and clamps coordinates — so a hand-crafted payload cannot exhaust memory in the recursive walk.
- A video's address is never trusted. Only YouTube and Vimeo are
recognised, by an allow-list of URL shapes; only the video id survives; and
the
srcthat reaches the iframe is rebuilt by the plugin from that id. The check is enforced in PHP on save (FB_Mind_Map_Data::parse_video()) — the copy in JavaScript exists so the editor can warn you as you type, and is not what protects the site. This is the same lesson as the HTML block: an attribute a user controls that ends up in asrcis a hole unless you rebuild the value yourself. - Colours go through
sanitize_hex_color()before they can reach a style attribute. - Almost nothing leaves your server. No CDN at runtime, no telemetry. The
one exception is a video block: the poster still comes from YouTube's image
host, and the player itself is loaded from
youtube-nocookie.comwhen someone presses play. Mind Maps → Settings turns the poster off; a site that wants zero external requests should not use video blocks.
Keyboard shortcuts
| Key | Action |
|---|---|
| Tab | Add a child of the selected node |
| Enter | Add a sibling below |
| F2 | Rename (double-click does the same) |
| Delete | Delete the node and its branch |
| ← ↑ ↓ → | Walk parent, siblings, children |
| Ctrl+Z | Undo · Ctrl+Shift+Z redo |
| Ctrl+C / X / V | Copy, cut, paste a branch |
| N | Add or edit this node's note |
| Ctrl+S | Save |
| Ctrl+0 | Fit to screen · Ctrl+± zoom |
Mouse: click selects, double-click a node renames it, double-click empty canvas adds a child there, right-click opens a context menu, dragging a node moves its whole branch, dragging the canvas pans, the wheel zooms toward the cursor (and scrolls a panel when the pointer is over one), and clicking the navigator jumps the viewport. Both floating panels drag by their header.
Languages
The plugin follows WordPress: set Settings → General → Site Language, or one
user's Profile → Language, and the plugin follows. A finished Turkish
translation ships in languages/.
The switcher in the editor toolbar is for the exception — someone who wants the
Mind Map screens in a different language from the rest of the site. It stores a
per-user choice and applies it through the plugin_locale filter, so
translations edited with Loco Translate still load normally.
That filter is necessary but, since WordPress 6.5, no longer sufficient.
WP_Translation_Controller stores loaded files as [ locale ][ text domain ].
plugin_locale still picks the right file, but the controller files it under
the chosen locale while every __() looks the domain up under the locale the
site is running in — so the file loads and the lookups miss it, silently. The
plugin therefore loads the chosen file a second time under the current locale;
FB_Mind_Map_I18N::load_override() is a dozen lines with the whole story in a
comment above it. Save Loco's
output to wp-content/languages/plugins/ and it survives a plugin update.
Mind Maps → Settings turns the switcher off, sets a site-wide language, and sets the autosave default.
Read the full README on GitHub →
Releases
| Tag | Published | Asset | Downloads |
|---|---|---|---|
| v0.3.0 | Aug 31, 2026 | fb-mind-map-0.3.0.zip | 8 |