Skip to main content

vsdxkit, by firmfooting

Create, edit and analyse Microsoft Visio .vsdx files with Python, with no Visio needed at runtime.

CI PyPI Python 3.15+ Licence: BSD-3-Clause Docs

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 main carries 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 .vsdx files 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.
  • .vsdm files can be read and saved, but only back to a .vsdm destination. The package kind is decided by the content type of visio/document.xml, not by the filename. So save() refuses a .vsdx destination for a macro-enabled package, and a .vsdm destination for one that is not. Either would produce a file whose extension and [Content_Types].xml disagree, which Visio reports as corrupt. Stripping macros to convert a .vsdm into a .vsdx is 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.PackageLimitError instead of exhausting process memory. The defaults suit documents from unknown sources; trusted callers can relax the caps with Document.open(filename, limits=PackageLimits(...)) or limits_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)

Source distribution for vsdxkit 0.12.0
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)

Table of built distributions (wheels) for vsdxkit 0.12.0
File Interpreter ABI Platform Actions
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 log

Release 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 log

Security 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.

Select from 2 files

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 log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page