Create, edit and analyse Microsoft Visio .vsdx files with Python, with no
Visio needed at runtime.
vsdxkit adds shape creation, Visio-faithful connectors, connector retargeting,
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.
The 1.0 API. This README describes the 1.0 API, which
maincarries and which is not yet released. Code written for 0.x will not run unchanged: Migrating to 1.0 gives the replacement for every 0.x name 1.0 removes. Install says which install gives you which API.
The distribution and the import package are both named vsdxkit. Import
each name from the module that defines it, for example from vsdxkit.document import Document; the package root re-exports nothing.
What it does
- Opens, queries and edits existing
.vsdxfiles without Microsoft Visio. - Finds shapes by ID, text or Shape Data, on a page or inside a group.
- Starts a new document from a blank template with
Document.new(). - Creates common flowchart shapes, or copies a shape already on the page.
- Creates dynamic or connection-point glue with straight, right-angle or curved routing.
- Retargets either end of an existing connector.
- Creates, 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.
- Builds a diagram from data, with no coordinates, using
vsdxkit.builder. - Saves to a new file or safely replaces the source file in place.
- Saves byte-identical files on every platform with
save(deterministic=True).
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.
Install
Python 3.15 or later is required. The tests run on Linux.
The 1.0 API is on main. It is not on PyPI yet. Install it from GitHub:
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. Until
1.0 is released, an install from main reports its version as 0.8.0, because
the version is only bumped when a release is cut.
The 0.x API is on PyPI. pip install vsdxkit installs 0.8.0, the latest
release, published on 2026-09-13. It has the 0.x names: it imports as vsdx,
and its document class is VisioFile. The examples below do not run on it. Pin
vsdxkit<1 to stay on the 0.x names once 1.0 is released, and read
Migrating to 1.0
when you move.
0.7.0 was withdrawn from PyPI because its Jinja rendering could run code from a crafted document. 0.7.1 and later render templates in Jinja's sandbox. The changelog has the details.
Quickstart
Open, edit and save
Opening reads the whole package into memory and holds no file, so there is nothing to close. Saving is explicit.
from vsdxkit.document import Document
document = Document.open("diagram.vsdx")
page = document.pages[0]
shape = page.shapes.by_text("Shape to remove")
if shape is not None:
shape.text = "Renamed shape"
document.save("edited.vsdx")
Call save() without a filename to replace the source file in place:
document = Document.open("diagram.vsdx")
document.pages[0].name = "Current state"
document.save()
A save writes every part you did not change exactly as it arrived. A part you did change is written as equivalent XML, but not in Visio's own spelling: the XML declaration, attribute quotes, empty-element form and namespace declarations can differ, and a CRLF inside text becomes LF. Visio and LibreOffice open both.
Find shapes
page.shapes is every shape on the page, inside groups too; page.children is
only the page's top-level shapes. A require_* lookup wants exactly one match
and raises NotFoundError for none, a by_* lookup returns None for none,
and a matching_* lookup returns every match. Any other test is a comprehension
over a collection.
document = Document.open("diagram.vsdx")
page = document.pages[0]
shape = page.shapes.require_text("Shape to remove")
missing = page.shapes.by_id("9999")
networked = page.shapes.matching_property("Network Name")
wide = [s for s in page.shapes if (s.width or 0) > 2]
assert missing is None and networked
Find pages and shapes covers pages, the scopes a lookup can search, what happens when several shapes match, and shape identity.
Create shapes and connectors
Coordinates are Visio page units, normally inches, and x and y identify the
shape centre. page.connect(source, target) returns a Connector.
from vsdxkit.document import Document
from vsdxkit.glue import Routing
from vsdxkit.shape_kind import ShapeKind
document = Document.open("diagram.vsdx")
page = document.pages[0]
start = page.create_shape(ShapeKind.START_END, x=2.0, y=6.0, text="Start")
work = page.create_shape(ShapeKind.PROCESS, x=6.0, y=6.0, text="Do the thing")
decision = page.create_shape(ShapeKind.DECISION, x=10.0, y=6.0, text="OK?")
page.connect(start, work)
page.connect(work, decision, routing=Routing.RIGHT_ANGLE)
document.save("flow.vsdx")
Create shapes and connectors lists the shape kinds and covers copying a shape, glue to connection points, routing, and finding connectors.
Retarget a connector
Name only the end that should move; the other stays where it is. The connector
keeps its glue and routing unless you pass options.
document = Document.open("flow.vsdx")
page = document.pages[0]
store = page.create_shape(ShapeKind.DATABASE, x=10.0, y=2.0, text="Store")
connector = page.connectors[0]
connector.retarget(target=store)
document.save("retargeted.vsdx")
shape.delete() also removes the connectors glued to the shape, and a group's
members with it.
Retarget a connector
covers connection points and options.
Work with swimlanes
Swimlane operations need a page that holds a Visio cross-functional flowchart
(CFF), one Visio drew or one made with Page.create_swimlanes(). add_lane()
copies the top lane above it and grows the CFF container to match.
document = Document.open("cross-functional-flow.vsdx")
page = document.pages[0]
diagram = page.require_swimlanes()
review_lane = diagram.add_lane("Review")
check = page.create_shape(ShapeKind.PROCESS, x=6.0, y=2.0, text="Check")
diagram.move_to_lane(check, review_lane)
assert diagram.lane_for(check) == review_lane
document.save("with-review-lane.vsdx")
Lane membership is geometric: a shape is in the lane whose band contains its centre, and there is no separate membership field to write. Cross-functional flowchart swimlanes has the rest.
Render a Jinja template
Jinja expressions stored in shape text are rendered into a new file:
document = Document.open("template.vsdx")
document.render(
context={"project": "Ward refurbishment", "owner": "Facilities"}
)
document.save("rendered.vsdx")
Rendering runs in Jinja's sandboxed environment.
Jinja templates covers
the group-shape loop and showif conventions, self assignments, and what the
sandbox does not protect against.
Documentation
The full documentation is at https://firmfooting.github.io/vsdxkit/.
| To | Read |
|---|---|
| Install, open a document and save it | Quick start |
| Select, add, copy and delete pages, and find shapes | Find pages and shapes |
| Create, connect, retarget and delete shapes | Create shapes and connectors |
| Extend a cross-functional flowchart | Cross-functional flowchart swimlanes |
| Build a diagram from data, with no coordinates | Diagrams from data |
| Fill a Visio template with data | Jinja templates |
| Look up a class or an error | API reference |
| Move code from 0.x to 1.0 | Migrating to 1.0 |
Limits
Document.new()starts from one blank page. Every other document starts from an existing.vsdx; the library does not build a package part by part..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()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 copies Visio's own cross-functional flowchart onto a page. It does not convert an ordinary page that has shapes on it into one.
- 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
vsdxkit.errors.PackageLimitErrorinstead of exhausting process memory. The defaults suit documents from unknown sources; trusted callers can relax the caps withDocument.open(filename, limits=PackageLimits(...))orlimits_path="vsdxkit.limits.json"(same keys, JSON object).
Contributing
Contributions are welcome. CONTRIBUTING.md covers the development environment, the checks to run before a pull request, the documentation build, when a change needs Visio, and how releases are cut. Report vulnerabilities privately, as SECURITY.md describes.
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 import package is vsdxkit, not vsdx, and the
licence remains BSD 3-Clause.
Part of firmfooting: safe, plain tooling for M365 and SharePoint operators. Not affiliated with or endorsed by Microsoft. Visio is a trademark of Microsoft.
Metadata
Release files for vsdxkit 0.12.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.12.0.tar.gz | 1.3 MB | With provenance | Details for vsdxkit-0.12.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| vsdxkit-0.12.0-py3-none-any.whl | Python 3 | none | any | Details for vsdxkit-0.12.0-py3-none-any.whl |
Total release size: 1.6 MB
Release files / vsdxkit-0.12.0.tar.gz
| Download URL | vsdxkit-0.12.0.tar.gz |
|---|---|
| Size | 1.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
57c008c6556b1d517fc25c84bd690312188472c7d0fe3b9f0b41bd1df2b0a4d7
|
|
BLAKE2b-256 checksum How to use checksums |
1ef8533dc159c97385b1eb64159e1a62903872c7feb4992dcc5467170f245990
|
| 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 Oct 16, 2026.
Transparency logRelease files / vsdxkit-0.12.0-py3-none-any.whl
| Download URL | vsdxkit-0.12.0-py3-none-any.whl |
|---|---|
| Size | 294.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
59a8aa8ebfa9eda1fdd51cecd1d458b4f2e81cbaedc989fc3a5a9005567b3c00
|
|
BLAKE2b-256 checksum How to use checksums |
19f956ddd5ac749de3fa67c7ac9c001065cfb4294df54bad2d20ce40900b8c98
|
| 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 Oct 16, 2026.
Transparency logSecurity for vsdxkit 0.12.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.12.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 Oct 16, 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.