PlantUML LocalRender
Table of Contents
Features
InstallationInstall from VS Code Marketplace (recommended)
You can also open the Marketplace page directly: Build from Source (for contributors)
Quick Start
See sample.md for a tour of diagram types, including error handling, and sample.puml for a PlantUML file of its own. Why PlantUML LocalPreviewing PlantUML in VS Code usually means one of two things: installing a Java
runtime to run PlantUML Local takes a third route: it renders
The extension bundles the official
UsagePlantUML Local renders most diagram types available in the bundled PlantUML browser engine, including sequence, use-case, class, state, activity and component diagrams. The first render after startup is the slow one — around 0.4 s on a typical development machine while the WebAssembly engine initialises. Later renders are considerably faster, though timings depend on the machine and on how complex the diagram is. If a diagram looks stale, run A changed diagram is drawn again as you type. For a large one,
A diagram whose Syntax highlighting
Tokens carry the usual TextMate scope names with a Files ending in In a The Outline view, the breadcrumbs and Go to Symbol in Editor (Ctrl+Shift+O;
Cmd+Shift+O on macOS) list the diagrams of a Previewing
|
| Command | What it does |
|---|---|
PlantUML Local: Export Diagram as SVG |
Writes the block under the cursor, or the only block in the file |
PlantUML Local: Export All Diagrams in File as SVG |
Writes every named block in the file |
PlantUML Local: Export All Diagrams as SVG and Update Image Links |
The above, then inserts or updates the image line after each block |
PlantUML Local: Export Diagram as PNG |
Writes the block under the cursor, or the only block in the file, as a PNG (below) |
PlantUML Local: Export All Diagrams in Folder as SVG |
Writes every named block of the Markdown and PlantUML files in a folder (below) |
PlantUML Local: Export All Diagrams in Folder as PNG |
The same, as PNGs |
The first four are also in the editor's right-click menu: the single exports appear with the cursor inside a block, the other two whenever the file contains a diagram — so the menu of an ordinary Markdown file stays untouched. Run from the command palette while the preview has focus, they use the only open Markdown document, or ask which one when several are open. The message each export ends with offers Show in Explorer, which selects the file it wrote, or the first of them, in the Explorer. One that reports a problem offers a way on instead: Show Problems for a diagram with an error, Export as SVG for a PNG that cannot be made, Open Settings for a setting to change, Show Output for the reasons a bulk export lists there, and Name Diagram… when blocks were left out for want of a name.
The word after the language — orders-api above — names the output file. It is
what ties a block to its SVG across edits, which a position could not: inserting
a diagram above would silently repoint everything below it. Naming is therefore
required for the bulk commands, and the single-diagram one asks when the block
has none — or when its name could not be a file name, since names are limited
to 128 ASCII letters, digits, hyphens and underscores, and cannot be a Windows
device name such as CON. The bulk commands report such a block as failed.
With the cursor on the opening line of a block that has no usable name, the
light bulb offers Name this diagram for export…: it asks for a name and
writes it on that line, as one edit that Undo takes back. In a .puml file it
does the same on the start line of any diagram, as (id=…) after
@startuml, @startmindmap and the rest. Nothing is written if the document
changed in the meantime.
All but the reference update also work in a .puml file, which writes each
of its diagrams to a file of its own. A diagram is named by an id on its start
line, @startuml(id=orders-api) or @startmindmap(id=roadmap); a file
holding a single diagram may leave it out, and the diagram is named after the
file. References are inserted into Markdown only.
A file already at the target is replaced only when you say so, or when the export put it there. If it holds the diagram already, nothing is written. If the export wrote it from the same document and it has not changed since, it is replaced without a question, so exporting an edited diagram again just updates its file. Anything else — a file put there by hand, another document's export, a file changed since or open with unsaved edits — the command asks about first, once for all the files it would replace, and the bulk commands can keep those files and write the rest. What the export wrote is kept in the workspace's state on this machine: each file, its document and a SHA-256 of the contents, no diagram. On another machine, the first export asks. A file that changes while the export runs — while its question is open, say — is left as it is and reported, whatever the answer. Each file is written under a temporary name beside the target and then renamed over it, so an export that fails part way never leaves half an image behind.
The #plantuml-local fragment on the inserted reference does two jobs. GitHub
ignores it and renders the SVG, while this extension's preview hides marked
images — the block above them already renders, and without that the same
diagram would appear twice (plantumlLocal.hideExportedImages turns this off,
for checking how the exported file itself looks). It also marks the line as
machine-managed: renaming a block or changing the export directory rewrites the
line on the next run, while every line without the marker — including a
hand-written reference to the same file — is never touched. The update command
is idempotent: running it twice changes nothing, and one Undo reverts whatever
it wrote. A block inside a block quote or a list item gets its reference inside
the same quote or item. If the document changes while the export runs, the files
are written but the references are left alone; run the command again.
Exports use the light palette regardless of your editor theme, since the files
face hosts whose background this extension does not control;
plantumlLocal.exportTheme pins dark or follows the preview instead. Each
exported SVG also carries an opaque background of its palette — the engine
leaves the canvas transparent, and a dark host page would otherwise show
through it.
Exported SVGs do not carry the diagram source. PlantUML embeds it in every SVG
it draws, so that an image can be decoded back into a diagram — comments,
preprocessor variables and hidden elements included, none of which the picture
shows — and the engine bundled here has no option to leave it out (PlantUML's
command line has -nometadata). The extension removes it, together with the
element names the engine records in data-* attributes. Keep the Markdown as
the source of a diagram: an exported file cannot be turned back into one.
A block that does not render is reported rather than exported. PlantUML answers a syntax error, an include it cannot resolve or an empty diagram with a drawing of the error in place of the diagram; instead of saving that as the block's SVG, the export commands count the block as failed and show PlantUML's message with the line of the document it points to. No file or reference is written for it.
Export Diagram as PNG writes the diagram as a PNG instead, for where an SVG
does not go, such as a slide or a chat. It is drawn at
plantumlLocal.exportPngScale times the diagram's own size — 1, 2 or 4, 2 by
default — on the background of its palette, into the same directory. The
extension host has no canvas to draw it on, so a panel opens beside the editor
for the time the drawing takes and closes itself; closing it stops the export.
A PNG larger than 8192 pixels a side or 16 million in all is refused rather
than shrunk, and the message names the largest scale it would fit at. Its text
is drawn with the fonts of the machine that exports it, where an SVG's is drawn
with those of whoever views it. The PNG export covers one diagram at a time and
inserts no reference.
Export All Diagrams in Folder as SVG, also on a folder's right-click menu in
the Explorer, does what Export All Diagrams in File as SVG does for every Markdown and
PlantUML file under a folder, each into its own export directory, with the
unsaved edits of open files. It skips .git, .hg, .svn, node_modules,
dist and build folders, follows no symbolic link or junction, and leaves out
a PlantUML file without a start line, which only other files include. It asks
before drawing, naming the documents, and then once for all the files it would
replace. A diagram headed for the same file as another — two documents beside
each other naming a block alike — is reported instead of written over it. A
folder of more than 500 documents or 2,000 named diagrams is refused rather than
exported in part, and cancelling while it draws writes nothing. Export All
Diagrams in Folder as PNG, beside it in the menu, writes PNGs instead, as
Export Diagram as PNG does, drawing them one after another in a single panel
that closes once the last is drawn.
plantumlLocal.exportDirectory (default images) decides where files go,
relative to the Markdown file rather than to the workspace root, so moving a
document keeps its diagrams beside it. Exporting writes files, so it needs a
trusted workspace — the preview does not.
Two things to keep in mind. The SVG is a snapshot: after editing a block,
re-run the command (commit the images/ directory alongside the document,
or the references point at nothing). And this flow is for hosts that do
not render PlantUML — one that does, like GitLab, renders the block itself
and would show the referenced image as a second copy.
Known Limitations
- Only the
azurestandard-library entry is bundled; other!include <…>libraries are unavailable !includeof a URL is not supported: URL-based directives are rejected with an inline message. Files are included only from the document's workspace folder, in a trusted workspace (see Including files)- Remote themes and other network resources are not supported
- Images (
<img:…>) are not loaded, local or remote: the engine draws an[img TBD…]placeholder in their place - Emoji (
<:smile:>) are not supported: their images are not bundled, and a diagram using one shows an inline message instead - A block draws one diagram, on one page: a block holding a second
@startuml…@enduml, or a diagram usingnewpage, shows a message instead, since the engine would draw only the first diagram or page - Functions that read the machine return nothing:
%getenv(),%filename()and%dirpath()are empty and%file_exists()is0 - Hyperlinks (
[[https://…]]) are drawn as text, not as links - A diagram wider or taller than 8192 pixels is not drawn; an error is shown instead
- Only the first error in a diagram is reported
- Start and end lines must be lowercase and begin with
@:@StartUmland\startumlfail - Four
!themethemes are not bundled:mars,sunlust,toyandvibrant - Features excluded from the bundled PlantUML browser build are unavailable.
Among them: embedded diagrams (
{{ … }}inside a note, a legend or a class body) and formulas (<math>,<latex>) are accepted but not drawn - Text is measured with approximate metrics (Node has no Canvas), so element widths, line wrapping and placement can differ slightly from plantuml.com
- A render that exceeds 30 seconds is terminated
- With the bundled PlantUML 1.2026.9, a diagram that includes more than 20 files, nested includes counted, shows an error instead: the engine draws the diagram once more for each included file it has not loaded yet and stops after 20 rounds (plantuml/plantuml#2943)
- Highlighting cannot tell which diagram type a
@startumlblock will turn into, so it colours the syntax of all of them: a line the engine rejects in one diagram type can still be coloured. Nor can it tell which names!procedureand!functiondefine: a call written without$stays plain - Other PlantUML extensions register the same language for the same file
extensions. With one of them installed too, which grammar colours a
.pumlfile depends on the order VS Code loads the extensions; disable one of them to choose
Configuration
| Setting | Default | Description |
|---|---|---|
plantumlLocal.theme |
auto |
Diagram palette. auto follows the VS Code theme; light / dark pin it |
plantumlLocal.logLevel |
info |
Floor for the rendering entries in the PlantUML Local output channel: the render worker and the diagrams of the Markdown preview. Commands such as export log regardless. VS Code's own channel level applies first — see Troubleshooting |
plantumlLocal.exportDirectory |
images |
Where exported SVGs and PNGs are written, relative to the Markdown file. . for the same folder; absolute paths and .. are rejected. Can be set per folder |
plantumlLocal.exportTheme |
light |
Palette for exported SVGs and PNGs, and for diagrams copied from the .puml preview. preview follows the palette the preview currently uses. Can be set per folder |
plantumlLocal.exportPngScale |
2 |
Scale of an exported PNG, and of a diagram copied from the .puml preview: 1, 2 or 4 times the diagram's own size. Can be set per folder |
plantumlLocal.hideExportedImages |
true |
Hide images marked #plantuml-local in the preview, so an exported diagram is not shown next to its block's render |
plantumlLocal.preview.updateMode |
onChange |
When a changed diagram is drawn again in the previews: onChange, onSave, or manual (with Clear Render Cache and Re-render). Can be set per folder |
plantumlLocal.diagnostics.enabled |
true |
List the problems of the diagrams in open Markdown files in the Problems panel; can be set per folder |
plantumlLocal.includePaths |
[] |
Folders a bare !include name is also looked for in, after the folder of the including file: relative to the workspace folder, up to 16. Can be set per folder; not used until the workspace is trusted |
A diagram that picks a !theme is drawn in that theme's colours, and most
themes paint no background of their own, leaving the diagram on the backdrop
of the palette. When the theme's text would be hard to read on that backdrop,
the diagram is drawn in the other palette instead — a theme made for a white
page, such as plain or cerulean, gets the light palette in a dark editor.
So does a diagram with the Azure icons and no theme, whose boxes are white
whatever the palette, and one whose own colours — set with skinparam, a
style, or on an element as in class Foo #pink — leave its text hard to read
in the dark palette: a diagram drawn in the dark palette is looked at once
drawn, and drawn in the light one when that reads better. This applies to the
preview and to exports alike, and counts a theme or the Azure icons that a
diagram gets from a file it includes: such a diagram is drawn once more in the
other palette when that file turns the choice. The .puml preview says when
it shows a diagram in the other palette. In the dark palette, links ([[…]])
are drawn in a light blue, as the engine's own blue is hard to read there.
How It Works
Rendering inside the preview webview — the way Mermaid extensions work — is not
an option for PlantUML: use-case, class and state layouts come from Graphviz,
which ships as WebAssembly, and the preview's Content-Security-Policy does not
grant wasm-unsafe-eval.
So the engine runs in a worker thread on the extension host (no CSP there), and only the finished, sanitised SVG is injected into the preview. PlantUML Local contributes no scripts to the Markdown preview — the sanitised SVG is the only thing it adds to the rendered document.
markdown-it's fence rule is synchronous while rendering is not; they meet
through a cache: the first pass shows a placeholder and starts rendering,
completion triggers one debounced preview refresh, and the second pass serves
the SVG from cache.
Security and Privacy
PlantUML Local is designed to render diagrams without sending their source to an external rendering service.
- Local Rendering: The engine, the Graphviz WebAssembly, the bundled icon library and all runtime assets ship inside the VSIX and load from disk
- No Telemetry: The extension collects no usage data and makes no intentional network requests
- Network Guard:
fetch/XMLHttpRequest/WebSocket/EventSourceare replaced with throwing stubs inside the render worker — a network attempt fails the render instead of making a request - Remote References Rejected:
!include https://…/!theme … from https://…render an explanatory message instead of reaching the engine - Worker Isolation: The engine's browser shims live in a worker thread, never on the extension host globals
- Render Timeout: A render exceeding 30 s is abandoned; the worker is terminated and restarted, and draws the renders that were waiting behind it
- Graphviz Crash Recovery: A diagram that makes Graphviz crash shows the engine's crash report, and the worker is restarted, so the diagrams after it are laid out by a fresh Graphviz
- SVG Sanitisation: Scripts, event handlers, non-fragment links, style sheets, meta refreshes and CSS that could load a resource are stripped before SVG reaches the preview. The one exception is a rasterised sprite, which must reach the preview as an inline
data:image/png— it is allowed on<image>only, must be base64 with no other characters, and must actually begin with the PNG signature - No Embedded Source: The copy of the diagram source that PlantUML embeds in every SVG, and the element names it records in
data-*attributes, are removed before an SVG reaches the preview or an exported file - Image-Only Panels: The
.pumlpreview, and the panel a PNG is drawn in, show the SVG as an image under a strict content security policy, so nothing in a diagram can run there - Included Files Checked: The engine reads no file itself; the extension hands it the file of a local
!includeonly from the document's workspace folder, through no symbolic link or junction, and only as UTF-8 text with one of the extensions listed under Including files - Untrusted Workspaces: No file is read for an
!includeuntil the workspace is trusted, and no processes are spawned
These controls reduce the extension's attack surface, but they have limits worth being explicit about: a worker thread is an isolation boundary for globals, not a process- or OS-level security sandbox, and stubbing browser-style network APIs is not the same as closing every network path available to Node.js.
CI verifies each package: verify-vsix unpacks the VSIX, checks for leaked key
material and rendering-service URLs, and renders a diagram from the packaged
worker.
For the full threat model and for vulnerability reporting, see SECURITY.md.
Platform Requirements
- VS Code 1.138 or later
- Windows, macOS or Linux, on x64 or ARM64
That's it — no Java runtime, no Graphviz install, no external tools.
Upgrading? The minimum is now 1.138 — it was 1.137 from 0.9.0, 1.136 from 0.8.0, 1.134 from 0.7.0, 1.125 from 0.4.0, and 1.101 before that. Older installations keep the version they have and stop receiving updates.
CI runs the test suite on Windows, macOS and Linux (x64 on Windows and Linux, ARM64 on macOS). The extension is plain JavaScript and WebAssembly, so other combinations are expected to work; please open an issue if one does not.
In a remote window (WSL, a Dev Container, SSH), PlantUML Local is installed on
the remote machine and runs there, so that is where diagrams are rendered and
exported files are written. The previews, and the panel a PNG is drawn in,
stay on your own computer: SVGs travel to them, and PNGs back, over VS Code's
own connection to that machine, and a PNG's text is drawn with your computer's
fonts. A diagram copied from the .puml preview goes to your own computer's
clipboard. No rendering service is involved either way.
Troubleshooting
- Stuck on "Rendering diagram…": Check Output → PlantUML Local for worker errors, then run
PlantUML Local: Clear Render Cache and Re-render - A red syntax-error box appears: The message comes from the PlantUML engine — only that diagram is affected, and the rest of the page still renders
- "An error has occurred!" with "For some reason, dot/GraphViz has crashed.": Graphviz could not lay out that diagram (packages nested a hundred deep are one way to get there). Only that diagram is affected, and it is an error, as a syntax error is: the preview says what to do, and it is not exported. Simplify it, or add
!pragma layout smetana, which lays out the diagram without Graphviz - Colours look wrong after switching themes: Backgrounds follow the palette the diagram was rendered with. If you pinned
plantumlLocal.theme, that palette wins by design - Exported images have different colours from the preview: Exports, the PNG and Copy buttons of the
.pumlpreview included, use the light palette by default, whatever the editor theme (plantumlLocal.exportTheme). Set it topreviewto export in the palette the preview uses, or setplantumlLocal.themetolightto preview diagrams in the colours they are exported in - Layout differs from plantuml.com: Text is measured with approximate metrics in the Node renderer, so box widths, line wrapping and element placement can differ slightly
- Setting
logLevel: debugshows nothing new: The channel is aLogOutputChannelnow, and VS Code decides what one of those shows — an extension cannot raise its own channel's level. Run Developer: Set Log Level and pick PlantUML Local; that choice is per channel and survives a restart.plantumlLocal.logLevelis a floor on top of it for the rendering entries, so it can only make those quieter
Contributing
Contributions are welcome — thank you for helping make PlantUML Local better 🙌 Please see CONTRIBUTING.md for guidelines.
If you're planning a larger change, opening an issue first is appreciated (it helps align direction and avoids duplicate work). Note that features requiring Java, a server or network access are out of scope by design.
Support & Maintenance Policy
PlantUML Local is a personal hobby project maintained in spare time. The project is active, but support is best-effort: I'll do my best to review issues and PRs, and releases may be a bit slow sometimes — thank you for your patience.
Helpful things when reporting bugs:
- OS / architecture / VS Code version
- The smallest PlantUML source that reproduces the issue
- Output from Output → PlantUML Local, with the level set to Debug via Developer: Set Log Level
Security-related reports should follow SECURITY.md. Really appreciate you using PlantUML Local 💛
License
PlantUML Local is licensed under the MIT License — see LICENSE.
The bundled engine @plantuml/core is MIT-licensed from version 1.2026.6 onwards (earlier versions are GPL-3.0-or-later). This extension's dependency range for it starts no lower than 1.2026.6 (see package.json), so it only ever resolves to MIT-licensed releases; the exact version a build used is recorded in package-lock.json.
Copyright and licence notices for the third-party code shipped inside the VSIX are collected in THIRD_PARTY_NOTICES.md.
Acknowledgments
- Diagram rendering powered by PlantUML and its
@plantuml/coreTeaVM build — this extension is a third-party project, not affiliated with or endorsed by the PlantUML project - Graphviz layout by Viz.js, a WebAssembly build of Graphviz shipped inside
@plantuml/core - DOM for the engine by happy-dom
- Extension framework by @kkdev92/vscode-ext-kit




