Skip to content

export_to_excalidraw drops all text elements #22

@clintandrewhall

Description

@clintandrewhall

Summary

When using the export_to_excalidraw tool from the Excalidraw MCP server, diagrams exported to excalidraw.com render shapes and arrows correctly but all text is missing — both standalone text elements and label properties on shapes/arrows.

Important

The Agent-- claude-opus-4.6-high-- produced this bug description based on what it had to do to resolve the issue. I've reviewed it, and it comports with what I saw, but it might not be the correct fix. YMMV, of course.

Environment

  • Client: Cursor IDE (VS Code fork)
  • MCP Server: excalidraw-mcp (remote, https://excalidraw-mcp-app.vercel.app/mcp)
  • Date: 2026-02-12

Steps to Reproduce

  1. Use the create_view tool to render a diagram with labeled shapes and standalone text elements using the simplified MCP element format:
[
  { "type": "rectangle", "id": "r1", "x": 100, "y": 100, "width": 200, "height": 80,
    "backgroundColor": "#a5d8ff", "fillStyle": "solid", "roundness": { "type": 3 },
    "strokeColor": "#4a9eed",
    "label": { "text": "My Label", "fontSize": 16 } },
  { "type": "text", "id": "t1", "x": 100, "y": 10,
    "text": "Title Text", "fontSize": 28, "strokeColor": "#1e1e1e" }
]
  1. The diagram renders correctly as far as the Agent is concerned via the create_view tool — all labels and text appear as expected-- but is simply not visible to the user (Diagram is not visible on Cursor #17).

  2. Use the export_to_excalidraw tool with the same element data (retrieved via read_checkpoint).

  3. Open the resulting excalidraw.com URL in a browser.

Expected Behavior

The exported diagram on excalidraw.com should match what was rendered inline — all text, labels, and arrow annotations should be visible.

Actual Behavior

The exported diagram on excalidraw.com shows only shapes and arrows. All text is missing:

  • Standalone text elements do not render.
  • label properties on rectangle, ellipse, diamond, and arrow elements do not render.
  • Arrow labels (e.g., "label": { "text": "connects" }) do not render.

Screenshot of the exported diagram with no text:

Image

Root Cause

The simplified MCP element format used by create_view is not the same as the native Excalidraw element format expected by excalidraw.com. The export_to_excalidraw tool appears to pass through the simplified JSON without converting it to proper Excalidraw format. Specifically:

1. Standalone text elements are missing required fields

The MCP simplified format allows:

{ "type": "text", "id": "t1", "x": 100, "y": 10,
  "text": "Hello", "fontSize": 20 }

But excalidraw.com requires all of the following fields for text to render:

{
  "id": "t1", "type": "text",
  "x": 100, "y": 10,
  "width": 110, "height": 25,
  "angle": 0,
  "strokeColor": "#1e1e1e",
  "backgroundColor": "transparent",
  "fillStyle": "solid",
  "strokeWidth": 2,
  "strokeStyle": "solid",
  "roughness": 1,
  "opacity": 100,
  "groupIds": [],
  "frameId": null,
  "roundness": null,
  "seed": 12345,
  "version": 1,
  "versionNonce": 67890,
  "isDeleted": false,
  "boundElements": null,
  "link": null,
  "locked": false,
  "text": "Hello",
  "rawText": "Hello",
  "fontSize": 20,
  "fontFamily": 1,
  "textAlign": "left",
  "verticalAlign": "top",
  "containerId": null,
  "originalText": "Hello",
  "autoResize": true,
  "lineHeight": 1.25
}

Missing fields like fontFamily, rawText, originalText, containerId, lineHeight, autoResize, width, and height cause excalidraw.com to silently drop the element.

2. The label shorthand on shapes is not a native Excalidraw property

The MCP format supports a convenient label property on shapes:

{ "type": "rectangle", "id": "r1", ..., "label": { "text": "Hello", "fontSize": 16 } }

But native Excalidraw does not have a label property. Instead, labeled shapes require two separate elements:

  1. The shape element, with a boundElements array referencing the text:
{
  "id": "r1", "type": "rectangle", ...,
  "boundElements": [{ "id": "r1_text", "type": "text" }]
}
  1. A separate text element bound to the shape via containerId:
{
  "id": "r1_text", "type": "text",
  "x": 120, "y": 130,
  "containerId": "r1",
  "textAlign": "center",
  "verticalAlign": "middle",
  "text": "Hello",
  "rawText": "Hello",
  "originalText": "Hello",
  "fontSize": 16,
  "fontFamily": 1,
  "lineHeight": 1.25,
  "autoResize": true
}

The x and y of the bound text must be explicitly calculated to center within the container:

x = container.x + (container.width - textWidth) / 2
y = container.y + (container.height - textHeight) / 2

3. Arrow labels have the same issue

Arrow label properties in the MCP format also need to be converted to separate bound text elements with containerId pointing to the arrow, positioned at the arrow's midpoint.

Workaround

To get text to display in the exported diagram, I had to:

  1. Navigate to the exported excalidraw.com URL (which loaded shapes/arrows into localStorage).
  2. Use JavaScript (page.evaluate) to:
    • Parse the existing elements from localStorage.getItem('excalidraw').
    • Add boundElements references to each shape/arrow that should have a label.
    • Create new, properly-formatted text elements with containerId bindings and calculated x/y positions.
    • Create standalone text elements with all required fields.
    • Write the updated elements back to localStorage.
  3. Reload the page.
  4. Use Excalidraw's native "Share > Export to Link" to generate a new URL.

Suggested Fix

The export_to_excalidraw tool should transform the simplified MCP element format into native Excalidraw format before uploading. This means:

  1. For standalone text elements: populate all required fields (rawText, originalText, fontFamily: 1, lineHeight: 1.25, autoResize: true, containerId: null, width, height, angle: 0, groupIds: [], etc.).

  2. For label on shapes: convert each label into a separate bound text element with:

    • A unique id (e.g., ${shapeId}_label).
    • containerId set to the shape's id.
    • textAlign: "center" and verticalAlign: "middle".
    • Calculated x/y centered within the container.
    • The shape's boundElements array updated to reference the new text element.
    • All other required text fields populated.
  3. For label on arrows: same as shapes, but position the text at the arrow's midpoint.

  4. For all elements: ensure common required fields are present (angle, seed, version, versionNonce, isDeleted, boundElements, groupIds, frameId, link, locked, updated).

The create_view tool already handles this conversion for inline rendering in Cursor — the same transformation logic should be applied in export_to_excalidraw.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions