AI-powered diagram generation using draw.io's built-in AI features and LLM integration
From drawio-diagrammingnpx claudepluginhub markus41/claude --plugin drawio-diagrammingThis skill uses the workspace's default tool permissions.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides agent creation for Claude Code plugins with file templates, frontmatter specs (name, description, model), triggering examples, system prompts, and best practices.
draw.io includes a native AI diagram generation feature accessible from the editor.
| Provider | Models | Notes |
|---|---|---|
| Google Gemini | 2.0 Flash, 2.5 Pro, 2.5 Flash | Free tier available |
| Anthropic Claude | 3.7 Sonnet, 4.0 Sonnet, 4.5 Sonnet | Requires API key |
| OpenAI GPT | 4o, 4.1, 4.1-mini, 5.1 | Requires API key |
Configure AI in draw.io via Extras > Configuration (JSON editor):
{
"enableAi": true,
"gptApiKey": "sk-...",
"geminiApiKey": "AIza...",
"claudeApiKey": "sk-ant-...",
"defaultAiModel": "claude-sonnet-4-20250514"
}
Or set via URL parameters:
https://app.diagrams.net/?enableAi=1
| Action | Description |
|---|---|
createPublic | Generate a new public diagram from prompt |
create | Generate a new diagram (private) |
update | Modify an existing diagram based on instructions |
assist | Get suggestions for improving the current diagram |
Point draw.io to a custom/self-hosted LLM endpoint:
{
"enableAi": true,
"aiBaseUrl": "https://your-llm-proxy.example.com/v1",
"aiApiKey": "your-key",
"aiModel": "your-model-name"
}
The endpoint must be compatible with the OpenAI Chat Completions API format.
When generating diagrams via LLM, always use the simplified format without the mxfile wrapper. This reduces complexity and error rates:
<!-- CORRECT: Simplified format for AI generation -->
<mxGraphModel>
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<!-- diagram content -->
</root>
</mxGraphModel>
<!-- AVOID: Full mxfile wrapper adds unnecessary complexity -->
<mxfile host="..." modified="..." agent="..." version="..." type="...">
<diagram id="..." name="...">
<mxGraphModel dx="..." dy="..." grid="..." ...>
<root>
<!-- diagram content -->
</root>
</mxGraphModel>
</diagram>
</mxfile>
Every generated diagram must start with the two mandatory structural cells:
<mxCell id="0"/> <!-- Root cell -->
<mxCell id="1" parent="0"/> <!-- Default layer -->
Omitting these causes the diagram to fail to load.
Always output uncompressed, human-readable XML. Never generate the base64-compressed format that draw.io uses internally for storage. draw.io imports uncompressed XML without issue.
Only use documented style property names. Common mistakes:
| Wrong | Correct |
|---|---|
fill | fillColor |
stroke | strokeColor |
color | fontColor |
border | strokeWidth |
background | fillColor |
bold | fontStyle=1 |
italic | fontStyle=2 |
font-size | fontSize |
text-align | align |
border-radius | rounded=1;arcSize=N; |
Every cell must have a unique id. Use descriptive IDs for clarity:
<!-- GOOD: Descriptive IDs -->
<mxCell id="api-gateway" value="API Gateway" .../>
<mxCell id="auth-service" value="Auth Service" .../>
<mxCell id="edge-gw-to-auth" edge="1" source="api-gateway" target="auth-service" .../>
<!-- ACCEPTABLE: Sequential IDs -->
<mxCell id="2" value="API Gateway" .../>
<mxCell id="3" value="Auth Service" .../>
<mxCell id="e1" edge="1" source="2" target="3" .../>
<!-- BAD: Duplicate IDs -->
<mxCell id="node" value="API Gateway" .../>
<mxCell id="node" value="Auth Service" .../> <!-- DUPLICATE -->
When using non-rectangular shapes, set the matching perimeter:
| Shape | Required Perimeter |
|---|---|
shape=ellipse | perimeter=ellipsePerimeter |
shape=rhombus | perimeter=rhombusPerimeter |
shape=hexagon | perimeter=hexagonPerimeter2 |
shape=triangle | perimeter=trianglePerimeter |
shape=parallelogram | perimeter=parallelogramPerimeter |
shape=trapezoid | perimeter=trapezoidPerimeter |
shape=step | perimeter=stepPerimeter |
Mismatched perimeters cause connection points to appear in wrong positions.
A good diagram generation prompt includes:
Create a cloud architecture diagram showing:
- A React frontend hosted on CloudFront/S3
- API Gateway routing to Lambda functions
- Lambda connects to DynamoDB and S3
- SQS queue between Lambda and a worker Lambda
- All inside a VPC with public and private subnets
- Use AWS icon shapes
- Blue color scheme, orthogonal edge routing
Create a UML sequence diagram for OAuth 2.0 authorization code flow:
- Participants: User, Client App, Auth Server, Resource Server
- Steps: authorization request, user login, auth code, token exchange, API call
- Show the redirect steps clearly
- Use dashed lines for responses
- Include activation boxes on Auth Server
Create a flowchart for a CI/CD pipeline:
- Start with code push
- Run linting and unit tests in parallel
- If tests pass, build Docker image
- Push to container registry
- Deploy to staging
- Run integration tests
- If pass, require manual approval
- Deploy to production
- End
- Use green for success paths, red for failure paths
- Horizontal swimlanes: Dev, CI, Staging, Production
Create an ER diagram with Crow's foot notation for an e-commerce database:
- Tables: users, products, orders, order_items, categories, reviews
- Show primary keys, foreign keys, and key columns
- Users have many orders (1:N)
- Orders have many order_items (1:N)
- Products have many order_items (M:N through order_items)
- Products belong to categories (N:1)
- Users write reviews on products (M:N through reviews)
- Use table-style entity boxes with column lists
| Avoid | Why | Better |
|---|---|---|
| "Make a diagram" | Too vague, no type specified | "Create a UML sequence diagram for..." |
| "Show everything" | Too broad, overwhelms the diagram | "Show the top-level components and their connections" |
| "Make it look nice" | Subjective, no actionable direction | "Use blue fill (#dae8fc), rounded corners, orthogonal edges" |
| "Add all the details" | Overloads the diagram | "Include service names, protocols, and port numbers" |
| Describing in paragraphs | Hard to extract structure | Use bullet points for entities and relationships |
After generating a diagram, verify all of the following:
[ ] 1. Structural cells: id="0" (root) and id="1" (default layer) exist
[ ] 2. All IDs are unique across the entire document
[ ] 3. Every vertex has vertex="1" and mxGeometry with width/height
[ ] 4. Every edge has edge="1" and mxGeometry with relative="1"
[ ] 5. Edge source/target IDs reference existing vertex cells
[ ] 6. Parent references point to existing cells
[ ] 7. Style properties use exact camelCase names (fillColor not fill-color)
[ ] 8. Perimeter type matches shape type
[ ] 9. HTML labels have proper XML escaping (< > &)
[ ] 10. Shapes don't overlap (distinct x, y positions)
[ ] 11. Shapes have reasonable dimensions (40-300px width, 30-200px height)
[ ] 12. Text is readable (fontSize >= 10 for labels)
[ ] 13. Colors are valid hex codes (#RRGGBB format)
[ ] 14. Edge routing style is specified for non-trivial diagrams
#!/usr/bin/env python3
"""validate_drawio.py - Validate AI-generated draw.io XML."""
import xml.etree.ElementTree as ET
import sys
import re
def validate_drawio(xml_string: str) -> list[str]:
"""Validate draw.io XML and return list of errors."""
errors = []
try:
root = ET.fromstring(xml_string)
except ET.ParseError as e:
return [f'XML parse error: {e}']
# Find the root element containing cells
if root.tag == 'mxfile':
models = root.findall('.//mxGraphModel')
elif root.tag == 'mxGraphModel':
models = [root]
else:
return [f'Unknown root element: {root.tag}']
for model in models:
root_elem = model.find('root')
if root_elem is None:
errors.append('Missing <root> element inside mxGraphModel')
continue
cells = {}
for cell in root_elem.iter('mxCell'):
cell_id = cell.get('id')
if cell_id is None:
errors.append('mxCell missing id attribute')
continue
if cell_id in cells:
errors.append(f'Duplicate cell id: {cell_id}')
cells[cell_id] = cell
# Also collect object-wrapped cells
for obj in root_elem.iter('object'):
obj_id = obj.get('id')
if obj_id is None:
errors.append('object element missing id attribute')
continue
if obj_id in cells:
errors.append(f'Duplicate id (object): {obj_id}')
inner = obj.find('mxCell')
if inner is not None:
cells[obj_id] = inner
# Check structural cells
if '0' not in cells:
errors.append('Missing structural cell id="0" (root)')
if '1' not in cells:
errors.append('Missing structural cell id="1" (default layer)')
# Validate each cell
for cell_id, cell in cells.items():
if cell_id in ('0', '1'):
continue
is_vertex = cell.get('vertex') == '1'
is_edge = cell.get('edge') == '1'
# Check parent reference
parent = cell.get('parent')
if parent and parent not in cells:
errors.append(
f'Cell {cell_id}: parent "{parent}" does not exist')
if is_vertex:
geo = cell.find('mxGeometry')
if geo is None:
errors.append(
f'Vertex {cell_id}: missing mxGeometry')
else:
w = geo.get('width')
h = geo.get('height')
if not w or not h:
errors.append(
f'Vertex {cell_id}: missing width/height')
if is_edge:
source = cell.get('source')
target = cell.get('target')
if source and source not in cells:
errors.append(
f'Edge {cell_id}: source "{source}" does not exist')
if target and target not in cells:
errors.append(
f'Edge {cell_id}: target "{target}" does not exist')
geo = cell.find('mxGeometry')
if geo is None:
errors.append(
f'Edge {cell_id}: missing mxGeometry')
elif geo.get('relative') != '1':
errors.append(
f'Edge {cell_id}: mxGeometry missing relative="1"')
# Validate style
style = cell.get('style', '')
if style:
# Check for common misspellings
misspellings = {
'fill=': 'fillColor=',
'stroke=': 'strokeColor=',
'color=': 'fontColor=',
'font-size=': 'fontSize=',
'font-family=': 'fontFamily=',
'font-color=': 'fontColor=',
'fill-color=': 'fillColor=',
'stroke-color=': 'strokeColor=',
'stroke-width=': 'strokeWidth=',
'border-radius=': 'arcSize=',
'text-align=': 'align=',
}
for wrong, correct in misspellings.items():
if wrong in style and correct not in style:
errors.append(
f'Cell {cell_id}: style uses "{wrong}" '
f'instead of "{correct}"')
# Check color format
color_props = re.findall(
r'(?:fill|stroke|font)Color=#([0-9a-fA-F]*)', style)
for color in color_props:
if len(color) not in (3, 6, 8):
errors.append(
f'Cell {cell_id}: invalid color #{color}')
return errors
if __name__ == '__main__':
if len(sys.argv) < 2:
print('Usage: python validate_drawio.py <file.drawio>')
sys.exit(1)
with open(sys.argv[1]) as f:
content = f.read()
errors = validate_drawio(content)
if errors:
print(f'INVALID: {len(errors)} error(s) found:')
for e in errors:
print(f' - {e}')
sys.exit(1)
else:
print('VALID: No errors found')
sys.exit(0)
Vibe Diagramming is a conversational, iterative approach to creating diagrams with AI. Instead of precise specifications, you describe what you want in natural language and refine through conversation.
"Move the database to the right side"
"Add arrows showing the data flow direction"
"Group the frontend services into a container labeled 'Presentation Layer'"
"Change all service boxes to use rounded corners and blue fill"
"Add labels on the edges showing the protocol (REST, gRPC, AMQP)"
"Make this look more like a hand-drawn sketch (enable sketch mode)"
"Add a legend in the bottom-right corner"
A structured approach for AI agents to iteratively improve diagram quality:
Prompt: Create a [diagram type] showing [description]
Output: First-pass XML
Check the generated diagram for:
Common improvements:
Apply identified improvements and regenerate the XML.
Run the validation script to catch structural errors.
Continue until quality threshold is met (typically 2-3 iterations).
For programmatic layout improvement:
def auto_layout_grid(cells: list, columns: int = 3,
cell_width: int = 160, cell_height: int = 80,
h_gap: int = 60, v_gap: int = 60,
start_x: int = 50, start_y: int = 50) -> list:
"""
Arrange cells in a grid layout.
Returns list of (cell_id, x, y) tuples.
"""
positions = []
for i, cell in enumerate(cells):
col = i % columns
row = i // columns
x = start_x + col * (cell_width + h_gap)
y = start_y + row * (cell_height + v_gap)
positions.append((cell['id'], x, y))
return positions
def auto_layout_tree(root_id: str, children: dict,
cell_width: int = 160, cell_height: int = 60,
h_gap: int = 40, v_gap: int = 80,
start_x: int = 400, start_y: int = 50) -> dict:
"""
Arrange cells in a top-down tree layout.
children: dict mapping parent_id -> [child_ids]
Returns dict of cell_id -> (x, y).
"""
positions = {}
level_counts = {}
def count_leaves(node_id):
kids = children.get(node_id, [])
if not kids:
return 1
return sum(count_leaves(k) for k in kids)
def layout(node_id, level, left_x):
kids = children.get(node_id, [])
if not kids:
x = left_x
y = start_y + level * (cell_height + v_gap)
positions[node_id] = (x, y)
return left_x + cell_width + h_gap
child_left = left_x
for kid in kids:
child_left = layout(kid, level + 1, child_left)
# Center parent over children
first_child_x = positions[kids[0]][0]
last_child_x = positions[kids[-1]][0]
parent_x = (first_child_x + last_child_x) // 2
y = start_y + level * (cell_height + v_gap)
positions[node_id] = (parent_x, y)
return child_left
layout(root_id, 0, start_x - (count_leaves(root_id) *
(cell_width + h_gap)) // 2)
return positions
When using draw.io with an MCP server (see mcp-integration skill), AI agents can programmatically create and modify diagrams through tool calls rather than generating raw XML.
This approach is less error-prone than generating full XML, as the MCP server handles validation and structure.
For complex diagrams, combine approaches:
| Error | Cause | Fix |
|---|---|---|
| Blank diagram | Missing structural cells | Add id="0" and id="1" cells |
| Shapes don't connect | Edge source/target reference wrong IDs | Verify edge source/target match vertex IDs |
| Invisible shapes | Missing vertex="1" | Add vertex="1" to all shape cells |
| Edges render as shapes | Missing edge="1" | Add edge="1" to connector cells |
| Connections attach to wrong spot | Perimeter mismatch | Match perimeter to shape type |
| Text not visible | fontColor same as fillColor | Use contrasting colors |
| Shapes stacked on origin | All x=0, y=0 | Set distinct x, y coordinates |
| Style not applied | Typo in property name | Use exact camelCase names from reference |
| HTML tags visible | Missing html=1 | Add html=1 to style string |
| Overlapping labels | No whiteSpace=wrap | Add whiteSpace=wrap;html=1; |
| XML parse error | Unescaped characters in labels | Escape <>&" in value attributes |