Creating Excalidraw Diagrams with Hermes Agent: JSON Format & Workflow
Hand-drawn Excalidraw JSON diagrams (arch, flow, seq).
Written by Neura Market from the official Hermes Agent documentation for Excalidraw. Commands, paths, and version numbers are reproduced from the source unchanged.
Read the official documentationExcalidraw Diagram Reference
This reference describes how to create diagrams by writing standard Excalidraw element JSON and saving them as .excalidraw files. These files can be opened on excalidraw.com without accounts or API keys.
When to Use
Use this tool for:
- Architecture diagrams
- Flowcharts
- Sequence diagrams
- Concept maps
- Any diagram that needs to be shared via a link or viewed/edited on excalidraw.com
Capabilities
- Generate
.excalidrawfiles from JSON element arrays - Drag-and-drop files onto excalidraw.com for viewing and editing
- Upload files to excalidraw.com for shareable links (no account needed)
- Create rectangles, ellipses, diamonds, arrows, and text elements
- Bind text to shapes and arrows as labels
- Connect arrows to shapes with fixed attachment points
- Control drawing order (z-order) via array order
- Set colors, fill styles, stroke styles, roundness, and arrowheads
Prerequisites
- The Excalidraw skill must be loaded.
- For uploading: the
cryptographypip package must be installed. Runpip install cryptographyin your terminal.
File Structure
Every .excalidraw file must use the following envelope structure. Replace ...your elements array here... with your actual array of element objects.
{
"type": "excalidraw",
"version": 2,
"source": "hermes-agent",
"elements": [ ...your elements array here... ],
"appState": {
"viewBackgroundColor": "#ffffff"
}
}
The canvas background is white by default.
Procedures
Create and Save a Diagram
- Load the Excalidraw skill (already done).
- Write an array of Excalidraw element objects.
- Wrap the array in the standard
.excalidrawenvelope shown above. - Save the file using
write_fileto a.excalidrawpath (e.g.,~/diagrams/my_diagram.excalidraw).
Upload for a Shareable Link
- Ensure the
cryptographypip package is installed (pip install cryptography). - Run the upload script via
terminal:python skills/creative/excalidraw/scripts/upload.py ~/diagrams/my_diagram.excalidraw - The script prints a shareable URL.
Create a Labeled Shape (Container Binding)
- Create the shape element with a
boundElementsarray containing an object withid(unique for the text) andtype:"text". - Create a text element with
containerIdset to the shape'sid, plus fields:id,x,y,width,height,text,fontSize,fontFamily(1),strokeColor,textAlign("center"),verticalAlign("middle"),originalText(same astext),autoResize(true). - Place the text element immediately after its container shape in the array.
- Do NOT use a
"label"property on shapes.
Example of a rectangle with rounded corners, blue fill, and bound text "Hello":
{ "type": "rectangle", "id": "r1", "x": 100, "y": 100, "width": 200, "height": 80,
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
"boundElements": [{ "id": "t_r1", "type": "text" }] },
{ "type": "text", "id": "t_r1", "x": 105, "y": 110, "width": 190, "height": 25,
"text": "Hello", "fontSize": 20, "fontFamily": 1, "strokeColor": "#1e1e1e",
"textAlign": "center", "verticalAlign": "middle",
"containerId": "r1", "originalText": "Hello", "autoResize": true }
Create a Labeled Arrow
- Create the arrow element with
boundElementscontaining an object withidandtype:"text". - Create a text element with
containerIdset to the arrow'sid, plus fields as for shape labels. - Place the text element immediately after the arrow in the array.
Example of an arrow labeled "connects":
{ "type": "arrow", "id": "a1", "x": 300, "y": 150, "width": 200, "height": 0,
"points": [[0,0],[200,0]], "endArrowhead": "arrow",
"boundElements": [{ "id": "t_a1", "type": "text" }] },
{ "type": "text", "id": "t_a1", "x": 370, "y": 130, "width": 60, "height": 20,
"text": "connects", "fontSize": 16, "fontFamily": 1, "strokeColor": "#1e1e1e",
"textAlign": "center", "verticalAlign": "middle",
"containerId": "a1", "originalText": "connects", "autoResize": true }
Connect an Arrow to Shapes
- Add
startBindingand/orendBindingobjects to the arrow element. - Each binding has
elementId(the shape'sid) andfixedPoint(array[x, y]where 0-1:top=[0.5,0],bottom=[0.5,1],left=[0,0.5],right=[1,0.5]).
Example of an arrow connected to shapes:
{
"type": "arrow", "id": "a1", "x": 300, "y": 150, "width": 150, "height": 0,
"points": [[0,0],[150,0]], "endArrowhead": "arrow",
"startBinding": { "elementId": "r1", "fixedPoint": [1, 0.5] },
"endBinding": { "elementId": "r2", "fixedPoint": [0, 0.5] }
}
Element Parameters
Common Parameters (All Elements)
| Parameter | Meaning | Required |
|---|---|---|
type | Element type: rectangle, ellipse, diamond, arrow, or text | Yes |
id | Unique string identifier for the element | Yes |
x | X coordinate of the element's left edge (for text, left edge; for shapes, top-left corner) | Yes |
y | Y coordinate of the element's top edge | Yes |
width | Width of the element | Yes |
height | Height of the element | Yes |
strokeColor | Color of the stroke/border (default "#1e1e1e") | No |
backgroundColor | Fill color (default "transparent") | No |
fillStyle | Fill style: "solid", etc. (default "solid") | No |
strokeWidth | Stroke width (default 2) | No |
roughness | Hand-drawn look level (default 1) | No |
opacity | Opacity percentage (default 100) | No |
Shape-Specific Parameters
| Parameter | Meaning | Required |
|---|---|---|
roundness | For rectangles: roundness: { "type": 3 } for rounded corners | No |
boundElements | Array of objects with id and type ("text") to bind a text label to a shape or arrow | For labeled shapes/arrows |
Text Element Parameters
| Parameter | Meaning | Required |
|---|---|---|
containerId | On a text element: the id of the shape or arrow it labels | For bound text |
text | The text content (for text elements) | For text elements |
fontSize | Font size in pixels (minimum 14; 16+ for body, 20+ for titles) | For text elements |
fontFamily | Font family (fontFamily: 1 = Virgil/hand-drawn) | For text elements (always 1) |
textAlign | Horizontal alignment (e.g., "center") | For bound text |
verticalAlign | Vertical alignment (e.g., "middle") | For bound text |
originalText | Should match text field | For text elements |
autoResize | Boolean (true recommended for text) | For text elements |
Arrow Parameters
| Parameter | Meaning | Required |
|---|---|---|
points | Array of [dx, dy] offsets from element x,y defining the arrow path | For arrows |
endArrowhead | Arrowhead style: null, "arrow", "bar", "dot", "triangle" | No |
strokeStyle | Line style: "solid" (default), "dashed", "dotted" | No |
startBinding | Object with elementId and fixedPoint to attach arrow start to a shape | No |
endBinding | Object with elementId and fixedPoint to attach arrow end to a shape | No |
Element Examples
Rectangle
{ "type": "rectangle", "id": "r1", "x": 100, "y": 100, "width": 200, "height": 100 }
Ellipse
{ "type": "ellipse", "id": "e1", "x": 100, "y": 100, "width": 150, "height": 150 }
Diamond
{ "type": "diamond", "id": "d1", "x": 100, "y": 100, "width": 150, "height": 150 }
Arrow (Unlabeled)
{ "type": "arrow", "id": "a1", "x": 300, "y": 150, "width": 200, "height": 0,
"points": [[0,0],[200,0]], "endArrowhead": "arrow" }
Standalone Text
{ "type": "text", "id": "t1", "x": 150, "y": 138, "text": "Hello", "fontSize": 20,
"fontFamily": 1, "strokeColor": "#1e1e1e", "originalText": "Hello", "autoResize": true }
For standalone text positioning, do not rely on textAlign or width for positioning. Use x = cx - (text.length * fontSize * 0.5) / 2.
Constraints and Caveats
- Do NOT use
"label": { "text": "..." }on shapes; it is silently ignored and results in blank shapes. - Text
x/y/width/heightfor bound text are approximate; Excalidraw recalculates on load. originalTextmust matchtextexactly.fontFamilymust be 1 (Virgil/hand-drawn).- Do NOT use emoji in text; they do not render.
- Minimum
fontSize: 14 (sparingly), 16 for body/labels, 20 for titles. - Minimum shape size: 120x60 for labeled rectangles/ellipses.
- Leave 20-30px gaps between elements.
- Prefer fewer, larger elements over many tiny ones.
- Text contrast: never use light gray on white; minimum text color on white is
#757575. - Array order determines z-order: first = back, last = front.
- Place bound text immediately after its container shape/arrow in the array.
- Emit elements progressively: background zones, shape, its text, its arrows, next shape.
- The upload script requires the
cryptographypip package. - Canvas background is white by default.
Color Palette
Refer to references/colors.md for a full palette. Common colors include:
#a5d8ff(Light Blue - primary/input)#b2f2bb(Light Green - success/output)#ffd8a8(Light Orange - warning/external)#d0bfff(Light Purple - processing/special)#ffc9c9(Light Red - error/critical)#fff3bf(Light Yellow - notes/decisions)#c3fae8(Light Teal - storage/data)
For dark mode considerations, see references/dark-mode.md. For more examples, see references/examples.md.
Failure Modes
- Using
"label"property on shapes results in blank shapes (silently ignored). - Emoji in text does not render.
- Low contrast text (e.g., light gray on white) is unreadable.
- Incorrect z-order (all rectangles first, then all texts, etc.) causes overlapping issues.
- Missing
boundElementsorcontainerIdbreaks label binding. fontFamilynot set to 1 may cause font rendering issues.fontSizebelow 14 may be unreadable.- Arrow
pointsnot matching element position may misplace the arrow. - Upload fails if
cryptographyis not installed.