Convert SVG files into editable draw.io diagrams.
Project description
svg-to-drawio
Turn any SVG into a real, editable draw.io diagram. Every shape, line, and text label becomes its own selectable cell - not a single flattened picture pasted onto the canvas.
- Truly editable output - rectangles, circles, paths, text, and groups all stay as native, movable draw.io cells.
- One engine, three ways in - the CLI, the Python API, and the desktop app all share the exact same conversion logic, so results are identical everywhere.
- Smart fallbacks, not silent failures - gradients, filters, masks, and clip-paths render natively whenever draw.io supports them, and fall back to a faithful embedded SVG image when it can't, instead of dropping detail.
- Built for batches - convert a single icon or an entire folder tree recursively, with watch mode, incremental caching, and structured diagnostics.
Quick start
Requirements: Python 3.11+, no external runtime dependency for the CLI.
pip install svg-to-drawio
svg-to-drawio diagram.svg
By default, output is written next to the source file (diagram.svg -> diagram.drawio).
Running from a repository checkout instead of an installed package works the same way:
python main.py diagram.svg
python main.py path/to/folder/ --recursive --overwrite
Desktop app
For anyone who would rather drag, drop, and click than type commands. The desktop app uses the exact same conversion engine as the CLI and Python API, so there is no difference in output quality.
Download a release artifact from the Releases page:
- Windows:
Setup.exeinstaller (auto-upgrades an existing install) or a plain.zipfor advanced users - Linux: portable
.AppImageor a plain.tar.gz - macOS:
.ziparchive of the app bundle
Features: drag-and-drop, multi-root queues, live progress, cooperative cancellation, safe close / force close, one-click output folder access, watch mode, persistent preferences, advanced rendering controls, a plain-English compatibility panel, and JSON report export.
Run it from source instead:
pip install -r requirements-desktop.txt
python desktop_app.py
CLI reference
svg-to-drawio [INPUT] [OPTIONS]
| Option | Description |
|---|---|
--output-dir DIR |
Write .drawio files into a separate directory |
--recursive |
Walk subfolders when the input is a directory |
--overwrite |
Replace existing .drawio outputs (skip by default) |
--stdout |
Write XML to stdout instead of a file (single file only) |
--watch |
Re-convert SVG files automatically whenever they change |
--flatten |
Dissolve <g> groups and emit all shapes at the root level |
--analyze |
Inspect compatibility and diagnostics without writing .drawio output |
--report-json PATH |
Write a structured JSON report with diagnostics, fallbacks, and compatibility data |
--no-cache |
Disable the persistent cache for unchanged inputs |
--max-elements N |
Warn and truncate output after N drawable elements |
--gradient-policy MODE |
auto, prefer-native, or prefer-fallback for multi-stop gradients |
--filter-policy MODE |
auto, prefer-native, or force-fallback for SVG filters |
--text-metrics-policy MODE |
auto, system, or heuristic for text sizing |
Examples
# Write output to a separate folder
svg-to-drawio src/icons/ --recursive --output-dir dist/diagrams --overwrite
# Watch a folder and reconvert on every save
svg-to-drawio src/ --watch --overwrite
# Analyze a file and emit a JSON report without generating a .drawio file
svg-to-drawio diagram.svg --analyze --report-json report.json
# Prefer editable native output over exact SVG filters and complex gradients
svg-to-drawio diagram.svg --filter-policy prefer-native --gradient-policy prefer-native
# Pipe draw.io XML directly into another tool
svg-to-drawio diagram.svg --stdout > diagram.drawio
# Flatten all groups into a single layer
svg-to-drawio diagram.svg --flatten --overwrite
During a normal conversion run, the CLI prints a short compatibility summary and mainly highlights rows that were not fully native. --analyze prints the full per-file compatibility matrix, and --report-json writes the same data in machine-readable form for CI, automation, or custom tooling.
Python API
from svg_to_drawio import RenderingOptions, convert_file, convert_to_string
# Write to disk and get the output path back
out = convert_file("diagram.svg")
# Return draw.io XML as a string without writing a file
xml = convert_to_string(
"diagram.svg",
rendering_options=RenderingOptions(
gradient_policy="prefer-native",
filter_policy="prefer-native",
text_metrics_policy="heuristic",
),
)
For batch conversions with progress reporting and cancellation:
from svg_to_drawio import ConversionOptions, ConversionService
service = ConversionService()
summary = service.convert(
["folder/", "other.svg"],
ConversionOptions(output_dir="out/", recursive=True, overwrite=True),
reporter=lambda event: print(event.message),
)
print(summary.to_status_line())
For one-off diagnostics without writing output files:
from svg_to_drawio import analyze_file
report = analyze_file("diagram.svg")
print(report.compatibility_score)
print(report.compatibility_overview.headline)
print(report.compatibility_overview.summary)
for row in report.compatibility_matrix:
print(row.label, row.status_label, row.message)
for issue in report.issues:
print(issue.message)
If you want to convert a file and still inspect the same structured report afterwards:
from svg_to_drawio import Converter
converter = Converter()
converter.convert_file("diagram.svg")
report = converter.get_report()
payload = report.to_dict() # JSON-friendly diagnostics + compatibility data
Advanced rendering
The engine exposes a small set of rendering policies shared by the CLI, Python API, and desktop app:
gradient_policy="auto"keeps the default behavior: use native multi-stop approximation when supported, otherwise fall back to embedded SVG.gradient_policy="prefer-native"keeps output editable even for unsupported multi-stop gradients by reducing them to draw.io's native two-color gradients when needed.gradient_policy="prefer-fallback"always preserves multi-stop gradients through embedded SVG fallback.filter_policy="auto"keeps the default behavior: nativefeDropShadowwhen supported, SVG fallback for unsupported filters.filter_policy="prefer-native"ignores unsupported filters instead of falling back so the surrounding shapes stay editable.filter_policy="force-fallback"always preserves filters through embedded SVG fallback.text_metrics_policy="auto"uses platform text metrics when available and a tuned heuristic otherwise.text_metrics_policy="system"explicitly prefers platform font metrics.text_metrics_policy="heuristic"keeps text sizing deterministic without consulting the system font backend.
What gets converted
| SVG element | draw.io output |
|---|---|
<rect> |
Rectangle cell (rounded corners from rx / ry) |
<circle> / <ellipse> |
Ellipse cell |
<line> |
Edge (no arrow by default) |
<polyline> |
Edge with waypoints |
<polygon> |
Filled stencil shape |
<path> |
Stencil; open unfilled paths with markers become edges; multi-stop linear gradients can be approximated natively |
<text> / <tspan> |
Text cell |
<image> |
Image cell with embedded asset data |
<g> |
Native draw.io group cell |
Inkscape layers (<g inkscape:groupmode="layer">) |
draw.io layer cell |
<a> |
Passes link=URL to child cells |
<use> / <symbol> |
Resolved from <defs> and rendered in place |
nested <svg> |
Handled with its own viewBox transform |
Supported CSS and SVG features
- CSS
<style>blocks: element, class (.cls), ID (#id), descendant (A B), child (A > B), multi-class (.a.b), and attribute selectors - CSS inheritance through
<g>groups and custom properties (var(--name, fallback)) currentColor,display:none, andvisibility:hidden- Transforms:
translate,scale,rotate,matrix,skewX,skewY viewBoxmapping withpreserveAspectRatioon both root and nested SVGs<defs>+<use>reuse- Linear and radial gradients with
gradientTransformandxlink:hrefinheritance - Multi-stop linear gradients on
<rect>,<circle>,<ellipse>, and<path>approximated natively as stacked two-color gradient bands - Multi-stop radial gradients on
<rect>,<circle>, and<ellipse>approximated as adaptive concentric rings marker-start,marker-end, andmarker-midwith closest draw.io arrow matchingopacity,fill-opacity,stroke-opacitystroke-dasharray,stroke-linecap,stroke-linejoin,fill-rule: evenodd- Text:
font-weight,font-style,font-size,font-family,text-anchor,text-decoration, approximatedominant-baseline <textPath>flattened into regular editable text near the original anchor point, with a compatibility warning- Embedded SVG fallback for
clip-path,mask, pattern fills, and unsupported filters so those fragments keep their appearance - Structured diagnostics, compatibility scoring, and a user-facing compatibility matrix for CLI, automation, and the desktop app
<title>-> draw.io tooltip;feDropShadow-> draw.io shadow style- Color formats: hex (
#rgb,#rgba,#rrggbb,#rrggbbaa),rgb(),rgba(),hsl(),hsla(),none,transparent - Local
<image>paths anddata:URIs (SVG, PNG); assets are embedded into the output
Limitations
<clipPath>,<mask>, pattern fills, and unsupported filters fall back to embedded SVG images for visual fidelity, so those fragments are less editable than native shapes.- Only
feDropShadowis mapped to native draw.io shadow styles; other filter effects use embedded SVG fallback when possible. - Multi-stop radial gradients on
<path>elements fall back to embedded SVG because draw.io's radial gradient always fills the whole cell bounding box. - Two-stop gradients are mapped directly to draw.io's native gradient properties.
stop-opacityis blended against white for all gradient types. - Multi-stop gradients combined with a CSS
filteror a shear transform fall back to embedded SVG because the filter or skew effect itself requires SVG. - The advanced rendering policies can intentionally trade exact fidelity for editability by keeping native shapes and simplifying unsupported gradients or filters.
- Text uses platform font metrics when available, with a tuned heuristic fallback in headless environments.
letter-spacingis reported when encountered, but draw.io text does not preserve it natively.<image>with shear-heavy transforms is approximated by its bounding box because draw.io image cells do not support true skew.- Local
<image>paths are resolved relative to the SVG file being converted and must stay inside the source SVG's folder tree; the resulting asset is embedded into the.drawiooutput so it stays self-contained. - Raster
<image>assets are wrapped in a tiny SVG before embedding because draw.io handles embedded SVGs more reliably than raw PNGs.
Transform rendering
| Transform | draw.io output |
|---|---|
translate, scale |
Native geometry offset / scaling |
rotate(theta) |
Native rotation=... style around the shape center |
skewX, skewY, or any matrix with shear |
Stencil with baked-in geometry |
Combined translate + rotate |
Native rotation with corrected center |
| Nested groups | All transforms accumulated before rendering |
Open, unfilled paths with SVG markers become draw.io edges so arrowheads stay editable.
Tip: re-exporting to SVG from draw.io
draw.io wraps text labels in <foreignObject> when exporting SVG, which many tools do not support. Before exporting:
- Edit -> Select All
- In the right-hand panel, click Convert labels to SVG
- File -> Export as -> SVG
Development
Run tests
python -m unittest discover -s tests -v
The checked-in fixture regression snapshot is intentionally generated with text_metrics_policy="heuristic" so it stays stable across Windows, Linux, and CI runners even when system font backends differ.
To regenerate the versioned fixture baseline deterministically:
python -m tests.regenerate_fixtures
Lint and type-check
python -m ruff check main.py svg_to_drawio svg_to_drawio_desktop tests
python -m mypy
Install dev tooling and Git hooks
pip install -r requirements-dev.txt
python -m pre_commit install --hook-type pre-commit --hook-type pre-push
python -m pre_commit run --all-files
Pre-commit runs ruff, mypy, and repository hygiene hooks on every commit. Tests run on every push, and GitHub Actions mirrors the same checks remotely.
Manual packaging (desktop app)
Build the base standalone bundle (Windows / Linux / macOS):
python build_desktop.py
This produces:
dist/desktop/svg-to-drawio.exeon Windowsdist/desktop/svg-to-drawioon Linuxdist/desktop/svg-to-drawio.appon macOS
On Windows, the plain .zip archive keeps this default portable onefile build. The Setup.exe installer uses a separate onedir bundle so the installed app starts faster once deployed.
Extra packaging layers are built on top of that base bundle:
- Windows installer:
packaging/windows/build_installer.ps1 - Linux AppImage:
packaging/linux/build_appimage.sh - macOS: archive only for now
Windows installer
The Windows installer is built with Inno Setup.
Install Inno Setup, for example:
winget install JRSoftware.InnoSetup
Then build the dedicated onedir installer bundle and wrap it into a Setup.exe:
env\Scripts\python.exe -m pip install -r requirements-desktop.txt
env\Scripts\python.exe build_desktop.py --bundle-mode onedir --dist-dir dist\desktop-installer
$version = env\Scripts\python.exe -c "from svg_to_drawio import __version__; print(__version__)"
.\packaging\windows\build_installer.ps1 -Version $version -InputDir "dist\desktop-installer\svg-to-drawio" -OutputDir "dist\release"
This produces:
dist\release\svg-to-drawio-<version>-setup.exe
Linux AppImage
An AppImage is not a system installer like Setup.exe on Windows. It is a portable Linux application bundle that you usually download, mark as executable, and run directly.
Build the base executable first, then package it with appimagetool:
python -m pip install -r requirements-desktop.txt
python build_desktop.py
curl -L \
-o appimagetool-x86_64.AppImage \
https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool-x86_64.AppImage packaging/linux/AppRun packaging/linux/build_appimage.sh
VERSION="$(python -c 'from svg_to_drawio import __version__; print(__version__)')"
./packaging/linux/build_appimage.sh \
dist/desktop/svg-to-drawio \
"$VERSION" \
./appimagetool-x86_64.AppImage \
"dist/release/svg-to-drawio-${VERSION}-linux-x86_64.AppImage"
In GitHub Actions, appimagetool is downloaded automatically by the workflow. For a local Linux build, you need to download it yourself first as shown above.
This produces:
dist/release/svg-to-drawio-<version>-linux-x86_64.AppImage
Typical end-user usage after downloading the AppImage from a release:
chmod +x svg-to-drawio-<version>-linux-x86_64.AppImage
./svg-to-drawio-<version>-linux-x86_64.AppImage
Some Linux systems require FUSE / libfuse.so.2 to run AppImages directly. If the AppImage fails to start with a FUSE-related error, install your distro's FUSE 2 compatibility package first, or extract the AppImage manually.
The plain .zip / .tar.gz archives remain available for advanced users who prefer to launch the raw bundle directly.
Project structure
main.py # CLI entry point
desktop_app.py # Desktop application entry point
build_desktop.py # PyInstaller bundle builder
packaging/ # Windows and Linux packaging assets
svg_to_drawio/
__init__.py # Public package exports
__main__.py # python -m svg_to_drawio entry point
cli.py # Installed console CLI
compatibility.py # User-facing compatibility matrix + overview builders
conversion_cache.py # Persistent cache for unchanged inputs
conversion_service.py # Shared batch service (CLI + desktop)
converter.py # Conversion orchestration
css.py # CSS parsing, selector matching, cascade
defs.py # <defs> index, gradients, markers, filters
diagnostics.py # Structured issues, assets, scores, JSON reports
drawio_model.py # Cell model + XML serialization
drawio_output.py # draw.io document wrapper
emitter_context.py # Per-conversion state shared by emitters
cell_factory.py # Cell construction helpers
element_geometry.py # Transformed bounds and geometry helpers
path_arcs.py # SVG arc conversion helpers
path_bounds.py # Tight bounding boxes for path commands
path_parser.py # SVG path tokenization and command parsing
path_simplification.py # Path simplification helpers
path_stencil.py # Path -> stencil serialization
path_types.py # Shared path type aliases
path_utils.py # Path helper facade
polygon_clip.py # Polygon clipping for gradient approximation
rendering_options.py # Shared rendering policy model
style_builder.py # draw.io style-string builder
styles.py # Visual property extraction
svg_fallback.py # Embedded SVG fallback generation
text_metrics.py # Text measurement backend selection
transforms.py # 2D affine transforms + viewBox mapping
utils.py # Shared parsing helpers
elements/
gradient_approx.py # Native multi-stop gradient approximation
image.py # <image> emitter
path.py # <path> emitter
poly.py # <polyline> / <polygon> emitters
shape_paths.py # Primitive path generation
shape_support.py # Shared stencil helpers
shapes.py # line, rect, circle, ellipse emitters
style_support.py # Shared emitter-side style helpers
text.py # <text> / <tspan> emitter
svg_to_drawio_desktop/
app.py # PySide6 main window
widgets.py # Drag-and-drop widgets
worker.py # Background conversion workers
tests/
unit/ # Rendering, styles, transforms, compatibility, fuzz
integration/ # CLI and end-to-end flows
License
See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file svg_to_drawio-3.4.0.tar.gz.
File metadata
- Download URL: svg_to_drawio-3.4.0.tar.gz
- Upload date:
- Size: 258.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
961b5e34bb53e859f93801ccc992415397e47a52e984e65e6eef76e6825bd82b
|
|
| MD5 |
34e4508fba088bedd09b33c53ace3b3d
|
|
| BLAKE2b-256 |
3d4e40dc39d597a791ae3321c5a97aad1e2b67835ae1e0a46f16c234134999b7
|
Provenance
The following attestation bundles were made for svg_to_drawio-3.4.0.tar.gz:
Publisher:
pypi-publish.yml on V1rg1lee/svg-to-drawio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
svg_to_drawio-3.4.0.tar.gz -
Subject digest:
961b5e34bb53e859f93801ccc992415397e47a52e984e65e6eef76e6825bd82b - Sigstore transparency entry: 1851931065
- Sigstore integration time:
-
Permalink:
V1rg1lee/svg-to-drawio@133521d9a2221d29d384bef7fbb5f5b8074e6a07 -
Branch / Tag:
refs/tags/v3.4.0 - Owner: https://github.com/V1rg1lee
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@133521d9a2221d29d384bef7fbb5f5b8074e6a07 -
Trigger Event:
release
-
Statement type:
File details
Details for the file svg_to_drawio-3.4.0-py3-none-any.whl.
File metadata
- Download URL: svg_to_drawio-3.4.0-py3-none-any.whl
- Upload date:
- Size: 93.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd78bad4d2949376d4c32a7dcea296f83c655b2f5f82e3edc3ba9b1dc5583556
|
|
| MD5 |
f9a8353a8d54f3720787400c84bd3524
|
|
| BLAKE2b-256 |
617183d146994dee086f94ecd6f65787eb9db1faa5066bc67d4c2816a6adb0e4
|
Provenance
The following attestation bundles were made for svg_to_drawio-3.4.0-py3-none-any.whl:
Publisher:
pypi-publish.yml on V1rg1lee/svg-to-drawio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
svg_to_drawio-3.4.0-py3-none-any.whl -
Subject digest:
cd78bad4d2949376d4c32a7dcea296f83c655b2f5f82e3edc3ba9b1dc5583556 - Sigstore transparency entry: 1851931149
- Sigstore integration time:
-
Permalink:
V1rg1lee/svg-to-drawio@133521d9a2221d29d384bef7fbb5f5b8074e6a07 -
Branch / Tag:
refs/tags/v3.4.0 - Owner: https://github.com/V1rg1lee
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@133521d9a2221d29d384bef7fbb5f5b8074e6a07 -
Trigger Event:
release
-
Statement type: