Skip to content

Instantly share code, notes, and snippets.

@okelet
Last active December 22, 2025 09:19
Show Gist options
  • Select an option

  • Save okelet/b104497b6a83f805c135c6ede2d17d75 to your computer and use it in GitHub Desktop.

Select an option

Save okelet/b104497b6a83f805c135c6ede2d17d75 to your computer and use it in GitHub Desktop.
Export drawio to multiple formats from command line

Draw.io Exporter

A command-line tool for automated batch export of draw.io diagrams to PNG and PDF formats.

Why This Tool Exists

Draw.io's built-in export functionality requires manual interaction to export each page individually, making it impractical for diagrams with multiple pages. This tool automates the process by:

  • Extracting page information from draw.io XML structure
  • Batch exporting all pages with proper naming conventions
  • Using actual page names from the diagram as output filenames
  • Supporting multiple output formats (PNG, PDF)

Features

  • Automated batch export: Export all pages in one command
  • Smart naming: Uses actual page names from diagrams as filenames
  • Multiple formats: PNG, single-page PDF, or multi-page PDF
  • Flexible output: Export to same directory or specify custom output location
  • Configurable borders: Adjust border size for exported images
  • Cross-platform: Works on macOS, Linux, and Windows

Requirements

  • Python 3.12+
  • draw.io desktop application installed
  • Dependencies: lxml, typer (automatically managed via uv)

Usage

Installation

No need to install. You can run this script directly from GitHub Gist using uv and let it manage the dependencies automatically.

Basic Usage

Export all pages to PNG in the same directory:

uv run --no-project --script https://gist.github.com/okelet/b104497b6a83f805c135c6ede2d17d75/raw/drawio-export diagram.drawio

Advanced Usage

Export to specific directory:

uv run --no-project --script https://gist.github.com/okelet/b104497b6a83f805c135c6ede2d17d75/raw/drawio-export diagram.drawio ./output

Export with custom border size:

uv run --no-project --script https://gist.github.com/okelet/b104497b6a83f805c135c6ede2d17d75/raw/drawio-export diagram.drawio --border 20

Export to multiple formats:

uv run --no-project --script https://gist.github.com/okelet/b104497b6a83f805c135c6ede2d17d75/raw/drawio-export diagram.drawio --format png --format pdf_one_page

Export multi-page PDF:

uv run --no-project --script https://gist.github.com/okelet/b104497b6a83f805c135c6ede2d17d75/raw/drawio-export diagram.drawio --format pdf_all_pages

Options

  • --format, -f: Output format (png, pdf_one_page, pdf_all_pages)
  • --border, -b: Border size in pixels (default: 15)
  • --verbose, -v: Enable verbose output
  • --help: Show help message

Output Naming

Files are named using the pattern: {base_name}-{page_number}-{page_name}.{extension}

  • Page numbers are zero-padded based on total pages (e.g., 01, 02 for 10+ pages)
  • Page names are sanitized for filesystem compatibility
  • Special characters are replaced with underscores or hyphens

Examples

For a diagram named architecture.drawio with pages "Overview" and "Details":

architecture-01-Overview.png
architecture-02-Details.png

Error Handling

The tool provides clear error messages for common issues:

  • Missing draw.io installation
  • Invalid input files
  • Export failures
  • XML parsing errors

Platform Support

Automatically detects draw.io installation on:

  • macOS: /Applications/draw.io.app/Contents/MacOS/draw.io
  • Linux/Windows: drawio or draw.io commands in PATH

AI Assistance 🤷‍♂️

Yes, I know, everyone uses AI these days. This script was partially developed with AI assistance, but it's been planned, reviewed, and thoroughly tested by humans. AI is here to help us write better code faster, not to replace good engineering practices. The tool works, it's been validated, and it solves a real problem - that's what matters.

#!/usr/bin/env -S uv -q run --no-project -s
"""
Draw.io Exporter
A command-line tool to export all pages from a draw.io file to multiple formats,
using the actual page names from the diagram as output filenames.
"""
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "lxml>=4.9.0",
# "lxml-stubs>=0.5.1",
# "typer>=0.9.0",
# ]
# ///
import os
import shutil
import subprocess
import sys
import tempfile
from enum import Enum
from pathlib import Path
from typing import Annotated
import typer
from lxml import etree
class OutputFormat(Enum):
"""
Enum representing the output format for the exported files.
"""
PNG = "png"
PDF_ONE_PAGE = "pdf_one_page"
PDF_ALL_PAGES = "pdf_all_pages"
def find_drawio_command() -> str:
"""
Find the draw.io executable on the system.
Searches for common draw.io command names and the macOS application path.
Returns:
str: Path to the draw.io executable
Raises:
typer.Exit: If draw.io command is not found
"""
# Try common command names
for cmd in ["drawio", "draw.io"]:
if shutil.which(cmd):
return cmd
# Try macOS application path
macos_path = "/Applications/draw.io.app/Contents/MacOS/draw.io"
if os.path.isfile(macos_path) and os.access(macos_path, os.X_OK):
return macos_path
typer.secho("Error: Draw.io command not found", fg=typer.colors.RED, err=True)
typer.secho("Please install draw.io from https://www.diagrams.net/", fg=typer.colors.YELLOW, err=True)
raise typer.Exit(code=1)
def get_page_info(xml_file: str) -> list[tuple[int, str]]:
"""
Extract page information from the draw.io XML file.
Parses the XML structure to find all diagram elements and extracts
their names and indices.
Args:
xml_file: Path to the XML file to parse
Returns:
list[tuple[int, str]]: List of tuples containing (page_index, sanitized_page_name)
Raises:
typer.Exit: If XML parsing fails
"""
try:
tree = etree.parse(xml_file)
root = tree.getroot()
# Find all diagram elements
diagrams = root.findall('.//diagram')
page_info: list[tuple[int, str]] = []
for idx, diagram in enumerate(diagrams):
# Get the page name from the 'name' attribute
page_name = diagram.get('name', f'page-{idx}')
# Sanitize the page name for use as filename
safe_name = "".join(
c if c.isalnum() or c in (' ', '-', '_') else '_'
for c in page_name
)
safe_name = safe_name.strip().replace(' ', '-')
if not safe_name:
safe_name = f'page-{idx}'
page_info.append((idx, safe_name))
return page_info
except Exception as e:
typer.secho(f"Error parsing XML: {e}", fg=typer.colors.RED, err=True)
raise typer.Exit(code=1)
def export_document(
drawio_cmd: str,
input_file: Path,
page_info: list[tuple[int, str]],
output_format: OutputFormat,
output_dir: Path | None = None,
border: int = 15
) -> None:
"""
Export each page to PNG with the page name as filename.
Args:
drawio_cmd: Path to the draw.io executable
input_file: Path to the input draw.io file
page_info: List of tuples containing (page_index, page_name)
output_dir: Optional directory for output files. If None, uses input file's directory
border: Border size in pixels for exported images (default: 15)
Raises:
typer.Exit: If any export operation fails
"""
base_name = input_file.stem
if output_dir:
output_path = output_dir
output_path.mkdir(parents=True, exist_ok=True)
else:
output_path = input_file.parent
if output_format in [OutputFormat.PDF_ONE_PAGE, OutputFormat.PDF_ALL_PAGES]:
output_extension = "pdf"
elif output_format == OutputFormat.PNG:
output_extension = "png"
else:
raise ValueError(f"Invalid output format: {output_format}")
if output_format == OutputFormat.PDF_ALL_PAGES:
output_file = output_path / f"{base_name}.{output_extension}"
typer.echo(f"Exporting {output_format.value.upper()} -> {output_file}")
export_command = [drawio_cmd, "--export", "--format", "pdf", "--all-pages", "--output", str(output_file), str(input_file)]
try:
_ = subprocess.run(
export_command,
check=True,
capture_output=True,
text=True
)
except subprocess.CalledProcessError as e:
typer.secho(f"Error exporting document: {e.stderr}", fg=typer.colors.RED, err=True)
raise typer.Exit(code=1)
else:
total_pages = len(page_info)
digits = len(str(total_pages))
for page_index, page_name in page_info:
output_file = output_path / f"{base_name}-{page_index+1:0{digits}d}-{page_name}.{output_extension}"
typer.echo(f"Exporting {output_format.value.upper()} page {page_index+1}: {page_name} -> {output_file}")
export_command = [drawio_cmd, "--export", "--format", output_format.value, "--page-index", str(page_index), "--border", str(border), "--output", str(output_file), str(input_file)]
try:
_ = subprocess.run(
export_command,
check=True,
capture_output=True,
text=True
)
except subprocess.CalledProcessError as e:
typer.secho(f"Error exporting page {page_index}: {e.stderr}", fg=typer.colors.RED, err=True)
raise typer.Exit(code=1)
def main(
input_file: Annotated[Path, typer.Argument(exists=True, file_okay=True, dir_okay=False, readable=True, help="Path to the draw.io file to export")],
output_formats: Annotated[list[OutputFormat], typer.Option("--format", "-f", help="Output format for exported files (default: png)")] = [OutputFormat.PNG],
output_dir: Annotated[Path | None, typer.Argument(file_okay=False, dir_okay=True, help="Optional output directory for PNG files (default: same as input file)")] = None,
border: Annotated[int, typer.Option("--border", "-b", min=0, help="Border size in pixels for exported images")] = 15,
verbose: Annotated[bool, typer.Option("--verbose", "-v", help="Enable verbose output")] = False,
) -> int:
"""
Export all pages from a draw.io file to multiple formats.
Examples:
Export to same directory as input file:
$ python script.py diagram.drawio
Export to specific directory:
$ python script.py diagram.drawio ./output
Export with custom border:
$ python script.py diagram.drawio --border 20
"""
# Find draw.io command
drawio_cmd = find_drawio_command()
if verbose:
typer.echo(f"Using draw.io command: {drawio_cmd}")
# Get pages info
with tempfile.NamedTemporaryFile(mode='w', suffix='.xml',) as tmp_file:
tmp_filename = tmp_file.name
try:
# Export to uncompressed XML
typer.echo(f"Exporting {input_file} to XML...")
_ = subprocess.run(
[drawio_cmd, "--export", "--format", "xml", "--output", tmp_filename, "--uncompressed", str(input_file)],
check=True,
capture_output=True,
text=True
)
# Parse XML to get page information
typer.echo("Analyzing pages...")
page_info = get_page_info(tmp_filename)
typer.secho(f"Found {len(page_info)} page(s)", fg=typer.colors.GREEN)
except subprocess.CalledProcessError as e:
typer.secho(f"Error running draw.io: {e.stderr}", fg=typer.colors.RED, err=True)
raise typer.Exit(code=1)
for output_format in output_formats:
typer.echo(f"Exporting {input_file} to {output_format.value}...")
export_document(drawio_cmd, input_file, page_info, output_format, output_dir, border)
typer.secho("✓ Export complete!", fg=typer.colors.GREEN, bold=True)
return 0
if __name__ == "__main__":
sys.exit(typer.run(main))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment