Diagram Export
Generate PlantUML and Mermaid diagrams from any machine definition.
Diagram Export
Every MachineNode can export its structure as a PlantUML or Mermaid diagram. This lets you visualize your state machines, generate documentation, and keep diagrams in sync with your code automatically.
Overview
The library provides two export methods on every MachineNode:
| Method | Format | Use Case |
|---|---|---|
machine.to_plantuml() |
PlantUML | Rich diagrams, IDE plugins, PDF generation |
machine.to_mermaid() |
Mermaid | GitHub README, Markdown docs, online editors |
Both methods return a string that you can print, save to a file, or embed in documentation.
PlantUML Export
Basic Example
from xstate_statemachine import create_machine
config = {
"id": "trafficLight",
"initial": "red",
"states": {
"red": {"on": {"TIMER": "green"}},
"green": {"on": {"TIMER": "yellow"}},
"yellow": {"on": {"TIMER": "red"}}
}
}
machine = create_machine(config)
plantuml = machine.to_plantuml()
print(plantuml)
Sample Output
@startuml
hide empty description
state "trafficLight" as trafficLight {
[*] --> trafficLight_red
state "red" as trafficLight_red
state "green" as trafficLight_green
state "yellow" as trafficLight_yellow
}
[*] --> trafficLight_red
trafficLight_red --> trafficLight_green : TIMER
trafficLight_green --> trafficLight_yellow : TIMER
trafficLight_yellow --> trafficLight_red : TIMER
@enduml
How to Render PlantUML
There are several ways to render PlantUML diagrams:
1. Save to file and use the PlantUML CLI:
pip install plantuml
# Save the diagram
with open("traffic_light.puml", "w") as f:
f.write(machine.to_plantuml())
# Render to PNG
plantuml traffic_light.puml
2. Use the PlantUML online server:
Paste the output at https://www.plantuml.com/plantuml/uml/
3. VS Code extension:
Install the “PlantUML” extension and preview .puml files directly in the editor.
Mermaid Export
Basic Example
from xstate_statemachine import create_machine
config = {
"id": "trafficLight",
"initial": "red",
"states": {
"red": {"on": {"TIMER": "green"}},
"green": {"on": {"TIMER": "yellow"}},
"yellow": {"on": {"TIMER": "red"}}
}
}
machine = create_machine(config)
mermaid = machine.to_mermaid()
print(mermaid)
Sample Output
stateDiagram-v2
[*] --> red
red --> green : TIMER
green --> yellow : TIMER
yellow --> red : TIMER
Embedding in GitHub README
Mermaid diagrams render natively in GitHub Markdown. Wrap the output in a fenced code block:
```mermaid
stateDiagram-v2
[*] --> red
red --> green : TIMER
green --> yellow : TIMER
yellow --> red : TIMER
```
Tip: You can automate this by writing a script that regenerates your README’s diagram section from your machine definition. This ensures your documentation always matches your code.
Export from JSON Machines
Both export methods work with any machine created via create_machine():
import json
from xstate_statemachine import create_machine
# Load config from a JSON file
with open("my_machine.json") as f:
config = json.load(f)
machine = create_machine(config)
# Export both formats
with open("docs/machine.puml", "w") as f:
f.write(machine.to_plantuml())
with open("docs/machine.mmd", "w") as f:
f.write(machine.to_mermaid())
print("Diagrams exported!")
Export from Pythonic API Machines
Machines built with the Pythonic API export diagrams the same way — StateMachine.create_machine(), build_machine(), and MachineBuilder.build() all return a MachineNode:
from xstate_statemachine import State, StateMachine
class DoorLock(StateMachine):
machine_id = "doorLock"
locked = State("locked", initial=True)
unlocked = State("unlocked")
unlock = locked.to(unlocked, event="UNLOCK")
lock = unlocked.to(locked, event="LOCK")
machine = DoorLock.create_machine()
# PlantUML
print(machine.to_plantuml())
# Mermaid
print(machine.to_mermaid())
Mermaid output:
stateDiagram-v2
[*] --> locked
locked --> unlocked : UNLOCK
unlocked --> locked : LOCK
Nested State Diagrams
Both exporters handle hierarchical (compound) states with proper nesting:
from xstate_statemachine import create_machine
config = {
"id": "editor",
"initial": "idle",
"states": {
"idle": {"on": {"OPEN": "editing"}},
"editing": {
"initial": "drafting",
"states": {
"drafting": {
"on": {
"PREVIEW": "previewing",
"SAVE": "saving"
}
},
"previewing": {"on": {"EDIT": "drafting"}},
"saving": {
"on": {"SAVE_DONE": "drafting"}
}
},
"on": {"CLOSE": "idle"}
}
}
}
machine = create_machine(config)
print(machine.to_plantuml())
PlantUML output (nested):
@startuml
hide empty description
state "editor" as editor {
[*] --> editor_idle
state "idle" as editor_idle
state "editing" as editor_editing {
[*] --> editor_editing_drafting
state "drafting" as editor_editing_drafting
state "previewing" as editor_editing_previewing
state "saving" as editor_editing_saving
}
}
[*] --> editor_idle
editor_idle --> editor_editing : OPEN
editor_editing_drafting --> editor_editing_previewing : PREVIEW
editor_editing_drafting --> editor_editing_saving : SAVE
editor_editing_previewing --> editor_editing_drafting : EDIT
editor_editing_saving --> editor_editing_drafting : SAVE_DONE
editor_editing --> editor_idle : CLOSE
@enduml
Parallel State Diagrams
Parallel states are also exported with their concurrent regions visible:
from xstate_statemachine import create_machine
config = {
"id": "player",
"initial": "playing",
"states": {
"playing": {
"type": "parallel",
"states": {
"video": {
"initial": "loading",
"states": {
"loading": {"on": {"VIDEO_READY": "streaming"}},
"streaming": {"on": {"BUFFER": "loading"}}
}
},
"audio": {
"initial": "muted",
"states": {
"muted": {"on": {"UNMUTE": "audible"}},
"audible": {"on": {"MUTE": "muted"}}
}
}
},
"on": {"STOP": "stopped"}
},
"stopped": {"type": "final"}
}
}
machine = create_machine(config)
print(machine.to_mermaid())
Using Diagrams for Documentation
Auto-Generate Documentation Script
Create a script that generates diagrams for all your machines:
"""generate_docs.py — Export diagrams for all state machines."""
import json
from pathlib import Path
from xstate_statemachine import create_machine
MACHINES_DIR = Path("configs")
DOCS_DIR = Path("docs/diagrams")
DOCS_DIR.mkdir(parents=True, exist_ok=True)
for config_file in MACHINES_DIR.glob("*.json"):
with open(config_file) as f:
config = json.load(f)
machine = create_machine(config)
machine_id = config["id"]
# Export PlantUML
puml_path = DOCS_DIR / f"{machine_id}.puml"
puml_path.write_text(machine.to_plantuml())
# Export Mermaid
mmd_path = DOCS_DIR / f"{machine_id}.mmd"
mmd_path.write_text(machine.to_mermaid())
print(f"Exported: {machine_id} -> {puml_path}, {mmd_path}")
print(f"\nAll diagrams written to {DOCS_DIR}/")
python generate_docs.py
Complete Example: Complex Machine with All Features
from xstate_statemachine import create_machine
config = {
"id": "orderSystem",
"initial": "browsing",
"context": {"cart": [], "total": 0},
"states": {
"browsing": {
"on": {
"ADD_ITEM": {"actions": "addToCart"},
"CHECKOUT": {"target": "checkout", "guard": "cartNotEmpty"}
}
},
"checkout": {
"initial": "shipping",
"states": {
"shipping": {
"on": {"SUBMIT_ADDRESS": "payment"}
},
"payment": {
"on": {"SUBMIT_PAYMENT": "processing"}
},
"processing": {
"invoke": {
"src": "processPayment",
"onDone": "confirmation",
"onError": "payment"
}
},
"confirmation": {"type": "final"}
},
"on": {"CANCEL": "browsing"},
"onDone": "complete"
},
"complete": {"type": "final"}
}
}
machine = create_machine(config)
# Save both diagram formats
Path("order_system.puml").write_text(machine.to_plantuml())
Path("order_system.mmd").write_text(machine.to_mermaid())
# Print Mermaid for quick preview
print(machine.to_mermaid())
Note: The
onDonetransition from compound states is included in the exported diagrams, showing the flow from the compound state to the next top-level state.