vsdxkit
Create, edit and analyse Microsoft Visio .vsdx files with Python. Visio is not required at runtime.
0.x API notice. 0.7 is the last release of the inherited API. 1.0 renames
VisioFiletoDocumentandContainertoSwimlaneDiagram, splitsConnectinto an internalConnectionRecordand a publicConnector, and drops the context manager: opening closes the archive before it returns, andsave()is the only write. The 1.0 design lists every change, and a migration guide lands atdocs/migration-1.0.rstwith the first breaking release. Once there is a release on PyPI again, pinvsdxkit<1to stay on the 0.x names; see Installation for what the index holds today.
The distribution is named vsdxkit. The import remains vsdx, so existing code keeps working.
vsdxkit adds shape creation, Visio-faithful connectors, connector re-anchoring, cross-functional flowchart swimlanes, stricter package handling, current Python tooling and typed public APIs. It began as a fork of dave-howard/vsdx and is now developed as its own project; see Provenance and licence.
What it does
- Opens, queries and edits existing
.vsdxfiles without Microsoft Visio. - Finds shapes by ID, text, regular expression or Shape Data.
- Creates common flowchart shapes from a bundled palette.
- Creates dynamic or connection-point glue with straight, right-angle or curved routing.
- Re-anchors either end of an existing connector.
- Reads and extends Visio cross-functional flowchart swimlanes.
- Copies shapes and pages while rewriting package-local IDs and importing masters.
- Renders data into Visio templates with Jinja.
- Saves to a new file or safely replaces the source file in place.
The implementation edits the XML parts inside the Open Packaging Convention archive. It does not drive the Visio user interface. Generated files are checked against Microsoft Visio through COM before a change that Visio could silently repair is considered done, and what Visio reported is recorded so CI can replay it without Visio. The COM check itself is manual; no workflow runs Visio. See When a change needs Visio.
Installation
As of 2026-09-13 there is no release on PyPI. The first release was
withdrawn after a security defect was found in it and cannot be republished, so
pip install vsdxkit finds no versions until the next one lands. The
PyPI project page shows the current state.
Install from GitHub in the meantime:
python -m pip install "vsdxkit @ git+https://github.com/firmfooting/vsdxkit.git"
That tracks main, so pin a commit if you need a reproducible install. Once
there is a release on the index, install it with pip install vsdxkit.
For development:
git clone https://github.com/firmfooting/vsdxkit.git
cd vsdxkit
uv sync --locked
Python 3.10–3.14 is supported on Linux, Windows and macOS. Add --group docs to
that sync if you also want to build the documentation; Sphinx needs Python 3.12
or later.
Open, edit and save
Use the context manager to close the package cleanly. Saving is explicit.
from vsdx import VisioFile
with VisioFile("diagram.vsdx") as vis:
page = vis.pages[0]
shape = page.find_shape_by_text("Shape to remove")
if shape is not None:
shape.text = "Renamed shape"
vis.save_vsdx("edited.vsdx")
Call save_vsdx() without a filename to replace the source file in place:
with VisioFile("diagram.vsdx") as vis:
vis.pages[0].name = "Current state"
vis.save_vsdx()
Create shapes and connectors
Shape coordinates are in Visio page units, normally inches. x and y identify the shape centre.
from vsdx import VisioFile
with VisioFile("diagram.vsdx") as vis:
page = vis.pages[0]
start = vis.create_shape(
page, "PALETTE_START_END", 2.0, 6.0, text="Start"
)
work = vis.create_shape(
page, "PALETTE_PROCESS", 6.0, 6.0, text="Do the thing"
)
decision = vis.create_shape(
page, "PALETTE_DECISION", 10.0, 6.0, text="OK?"
)
page.connect_shapes(start, work)
page.connect_shapes(work, decision, route="rightangle")
vis.save_vsdx("flow.vsdx")
Bundled palette names are:
PALETTE_PROCESSPALETTE_DECISIONPALETTE_START_ENDPALETTE_PARALLELOGRAMPALETTE_DATABASE
Connector route combines glue and routing behaviour:
| Value | Meaning |
|---|---|
dynamic |
Dynamic shape glue. This is the default. |
point |
Glue to zero-based connection points selected with from_cp and to_cp. |
straight |
Dynamic glue with straight routing. |
rightangle |
Dynamic glue with right-angle routing. |
curved |
Dynamic glue with curved routing. |
| `point | curved` |
Re-anchor a connector
Pass only the end that should move. A None endpoint keeps the current shape.
with VisioFile("flow.vsdx") as vis:
page = vis.pages[0]
store = vis.create_shape(
page, "PALETTE_DATABASE", 10.0, 2.0, text="Store"
)
# a connector is the shape a Connect record points from
connector = page.find_shape_by_id(page.connects[0].from_id)
page.reanchor_connector(connector, to_shape=store)
vis.save_vsdx("reanchored.vsdx")
Deleting a shape through page.delete_shape(shape) also removes incident connectors and their Connect records.
Work with swimlanes
Swimlane operations require an existing Visio cross-functional flowchart (CFF) page. add_swimlane() clones the current top lane and updates the CFF container geometry.
with VisioFile("cross-functional-flow.vsdx") as vis:
page = vis.pages[0]
container = page.get_container()
if container is None:
raise ValueError("The page is not a Visio CFF diagram")
review_lane = page.add_swimlane("Review")
check = vis.create_shape(
page, "PALETTE_PROCESS", 6.0, 2.0, text="Check"
)
page.add_shape_to_lane(check, review_lane)
assert container.lane_of(check) is not None
vis.save_vsdx("with-review-lane.vsdx")
Visio CFF membership is geometric. Shapes are associated with the lane whose vertical band contains their centre; there is no separate membership field to write.
Render a Jinja template
Jinja expressions can be stored in shape text and rendered into a new file:
with VisioFile("template.vsdx") as vis:
vis.jinja_render_vsdx(
context={"project": "Ward refurbishment", "owner": "Facilities"}
)
vis.save_vsdx("rendered.vsdx")
The package also supports its existing group-shape loop and showif conventions. See docs/templating.rst and the tests/test_jinja*.py cases for the exact template structure.
Limits
- The library starts from an existing
.vsdx; it does not create a complete Visio document package from nothing. .vsdmfiles can be read and saved, but only back to a.vsdmdestination. The package kind is decided by the content type ofvisio/document.xml, not by the filename, sosave_vsdx()refuses a.vsdxdestination for a macro-enabled package and a.vsdmdestination for one that is not — either would produce a file whose extension and[Content_Types].xmldisagree, which Visio reports as corrupt. Stripping macros to convert a.vsdminto a.vsdxis not supported. A destination with no extension, or with an unrelated one, gets the matching Visio extension appended.- Swimlane creation works on existing Visio CFF diagrams. It does not convert an ordinary page into a CFF diagram.
- Visio may recalculate layout when a generated file opens. The library writes the glue and route cells but does not reproduce Visio's entire layout engine.
- Loading enforces package expansion limits before any archive member is read: at most 512 members, 64 MiB per member, 256 MiB total uncompressed, and a 100:1 compression ratio, plus rejection of duplicate and path-unsafe member names. A hostile or accidental archive is refused with
vsdx.PackageLimitErrorinstead of exhausting process memory. The defaults suit documents from unknown sources; trusted callers can relax the caps withVisioFile(filename, limits=PackageLimits(...))orlimits_path="vsdx.limits.json"(same keys, JSON object).
Development and verification
uv run --no-sync python -m pytest tests -q
uv run --no-sync ruff check vsdx tests tools
uv run --no-sync ruff format --check vsdx tests tools
uv run --no-sync pyrefly check vsdx --min-severity warn --output-format min-text
uv run --no-sync sphinx-build -W --keep-going -b html docs docs/_build/html
uv run --no-sync python -m build
The package is held at pyrefly's strict preset. CI tests Python 3.10–3.14 on Linux and Windows, and 3.10 and 3.14 on macOS. Changes that Visio could silently repair also run through tools/visio_verify.py, which opens the file in an invisible Microsoft Visio instance and diffs what Visio reports against what the package declares. That step is manual, on a maintainer's Windows machine; GitHub's runners have no Visio. What Visio said is recorded under tests/fixtures/visio_observations/, and CI replays those recordings against the fixtures on every run.
Documentation
The Sphinx source is in docs/. Build it locally with:
uv sync --locked --group docs
uv run --no-sync python -m sphinx -W --keep-going -b html docs docs/_build/html
Sphinx is pinned in the docs dependency group, which requires Python 3.12 or
later. The library itself still supports 3.10, so run the docs build on a 3.12+
interpreter.
Provenance and licence
vsdxkit descends from dave-howard/vsdx, originally written by Dave Howard and released under the BSD 3-Clause licence. That work is the foundation this library is built on, and its copyright notice is retained in LICENSE alongside our own.
vsdxkit is now developed independently: it is not a downstream of that project and does not track it. The vsdx import namespace is kept so existing code continues to work, and the licence remains BSD 3-Clause.
Metadata
Release files for vsdxkit 0.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| vsdxkit-0.8.0.tar.gz | 765.7 kB | With provenance | Details for vsdxkit-0.8.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| vsdxkit-0.8.0-py3-none-any.whl | Python 3 | none | any | Details for vsdxkit-0.8.0-py3-none-any.whl |
Total release size: 883.1 kB
Release files / vsdxkit-0.8.0.tar.gz
| Download URL | vsdxkit-0.8.0.tar.gz |
|---|---|
| Size | 765.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a72a6fcc238c0dd54f69de485e6e1f9d98396a8a520d8429c8a533ce147c4a27
|
|
BLAKE2b-256 checksum How to use checksums |
def8b51b07daa6838916ad6a5f3c32b04c492d518a8a88eb5378f98e75ad1cab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logRelease files / vsdxkit-0.8.0-py3-none-any.whl
| Download URL | vsdxkit-0.8.0-py3-none-any.whl |
|---|---|
| Size | 117.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f1ad4ac42cd5d4e02c9f009aa1af6f89d3b65e8800b9e319ab51732681dfb416
|
|
BLAKE2b-256 checksum How to use checksums |
50900b608b3541ac099f1eb70e706e6e80bb05e6a979f72fd830dfc297166007
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logSecurity for vsdxkit 0.8.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for vsdxkit-0.8.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 13, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.