# Build a Real-Time Excalidraw-like Collaborative Canvas using Velt MCP and Antigravity🎉

> Source: <https://dev.to/studio1hq/build-a-real-time-excalidraw-like-collaborative-canvas-using-velt-mcp-and-antigravity-j3f>
> Published: 2026-05-21 16:43:40+00:00

In this tutorial, we’ll build an Excalidraw-style collaborative whiteboard using Next.js, HTML5 Canvas, and Velt. You’ll add real-time features like live cursors, comments, presence, and huddles directly into your app. Instead of wiring everything manually, we’ll use Velt MCP and AI agents to handle the integration. We’ll also look at how CRDT-based sync keeps everything in real time.

By the end, you’ll have a fully working multi-user canvas app with production-ready collaboration built in.

## What we are building

- Excalidraw-style infinite whiteboard
- Real-time collaboration with cursors, comments, huddle, notifications, and presence
- Multi-user canvas with shared state

## Why add collaboration to Canvas apps

-
**Single-user by default:** Most canvas apps work locally and don’t support multiple users out of the box -**Real-time sync is complex:** Handling state sync, conflicts, and updates across users is not trivial -**Lack of shared context:** Without comments, cursors, and presence, collaboration feels disconnected

## Why use Velt

[Velt is a collaboration SDK](https://velt.dev/) that lets you add real-time, multi-user features directly into your app without building the backend infrastructure yourself. It handles presence, syncing, communication, and UI components out of the box, so you can focus on your product.

-**Drop-in collaboration layer:** Add features like comments, cursors, and presence without building from scratch -**Real-time features built in:** Cursors, comments, presence, notifications, and huddles -**CRDT-based sync support:** Enables conflict-free real-time state updates for multi-user apps -**AI-powered setup with MCP:** Use Velt MCP and agent skills to automatically install and configure features -**No infra needed:** No need to manage WebSockets, sync engines, or backend services -**Customizable UI components:** Easily integrate collaboration UI into your existing design system

## Prerequisites

- Node.js 18+
- Velt API key (from Velt dashboard)
- AI coding editor (Anitgravity is used in this demo)
- Basic React and TypeScript knowledge**Setting up the project**- Clone the repository -
[https://github.com/Studio1HQ/Velt-Demos/tree/main/excalidraw-velt-demo](https://github.com/Studio1HQ/Velt-Demos/tree/main/excalidraw-velt-demo) - Run
`npm install`

- And then run
`npm run dev`

- Now, open
[http://localhost:3000](http://localhost:3000/)

## Tutorial: Building Velt-powered Excalidraw-like App

### Step 1: Set up Velt MCP

Velt MCP lets your editor (Antigravity) run the Velt installer and guide the integration.

Now, add the Velt MCP installer to Antigravity using the command below:

```
npx -y @velt-js/mcp-installer
```

Add it to the Antigravity MCP server configuration with** command: "npx"**and**.**`args: ["-y", "@velt-js/mcp-installer"]`

Also, [Velt Agent Skills](https://docs.velt.dev/get-started/skills) guides the AI on what to implement using best practices, while MCP gives it access to the tools needed to actually execute those changes. Together, they make the integration accurate, structured, and reliable. We have both installed and will be used accordingly.**Get your Velt API key**:

- Go to the Velt Dashboard
- Create a project
- Copy your API key

Add it to your `.env`

:

```
NEXT_PUBLIC_VELT_API_KEY=your_api_key_here
```

### Step 2: Start Velt installation using AI

Now that MCP is set up, we can let the AI agent handle the Velt integration for us.

Open your editor (Antigravity) and type:

```
install velt
```

This triggers the Velt MCP installer, which runs as a guided setup inside your editor.

Instead of manually adding SDKs and wiring things, the agent walks you through the setup step by step.

It will ask you for a few inputs:

- Your**project directory**- Your** Velt API key and auth token**- The** features you want to enable**(comments, presence, cursors, CRDT, etc.) - Where to place the** VeltProvider**(recommended:`app/page.tsx`

) - UI placement preferences (like corner position)

You can answer each step directly in chat. The flow is simple and guided.

### Step 3: Provide the prompt for MCP

At this point, we already have a working whiteboard built manually. Now, instead of integrating Velt step by step ourselves, we use Velt Agent Skills to analyze this existing app and plan how collaboration should be added.

In your editor, after running `install velt`

, provide the following prompt:

I want to start the Velt integration. Review my project structure and use your Velt Agent Skills to plan the CRDT store implementation.

Once you provide this, the agent starts analyzing your codebase. It looks at how your canvas is structured, how state is managed, and where real-time sync can be introduced. Based on this, it generates an integration plan tailored to your whiteboard.

Instead of manually deciding how to structure CRDT or where to wire Velt, the agent uses its skills to plan it correctly for your app.

After reviewing the plan, you can approve it, and the agent will apply the changes step by step.

### Step 4: Understand the existing project structure

Before we look at what MCP added, let us understand how this project is structured. Since the agent analyzes your codebase before integrating Velt, this gives context for what it is working with.

The [project is organized into three main folders](https://github.com/Studio1HQ/Velt-Demos/tree/main/excalidraw-velt-demo): `app`

, `components`

, and `lib`

.

- The
`app/`

folder contains the core application logic. This is where the whiteboard is rendered and all canvas interactions like drawing, selecting, and updating elements are handled. - The
`components/`

folder contains UI elements and collaboration integrations. This is where Velt features are connected to your app, including user identity, comments, and UI-level controls. - The
`lib/`

folder handles state management and shared logic. It manages canvas data, document context, and sync-ready state, making it easier to extend the app with real-time collaboration.

### Step 5: Add real-time collaboration features (Using MCP)

Now that the whiteboard is working, we layer Velt on top to make it collaborative. This is where users start seeing each other, interacting in real time, and sharing context.

After you provide the prompt and approve the plan, the MCP installer integrates Velt into your project. It sets up the foundation required for collaboration to work correctly with your existing whiteboard.

### Presence and cursors

In [ app/layout.tsx](https://github.com/Studio1HQ/Velt-Demos/blob/main/excalidraw-velt-demo/app/layout.tsx), the

`VeltProvider`

enables real-time awareness across your app. Then in `VeltSetup.tsx`

, each user is identified using. This is what allows Velt to track who is online.Learn more [here](https://docs.velt.dev/realtime-collaboration/presence/overview)

``` python
'use client'
import React, { useEffect, useState, Suspense } from "react";
import {
  VeltProvider,
  useSetDocument,
  VeltCursor,
  useVeltClient,
} from "@veltdev/react";
import { useCurrentDocument } from "@/lib/useCurrentDocument";
import { TEST_USERS } from "@/lib/users";
import { useSearchParams } from "next/navigation";

function VeltIdentity({ children }: { children: React.ReactNode }) {
  const { documentId } = useCurrentDocument();
  useSetDocument(documentId ?? "default-whiteboard");
  return <>{children}</>;
}

function VeltProviderInner({ children }: { children: React.ReactNode }) {
  const searchParams = useSearchParams();
  const [user, setUser] = useState(TEST_USERS[0]);

  useEffect(() => {
    const userIndex = searchParams.get("user");
    if (userIndex) {
      const index = parseInt(userIndex);
      if (!isNaN(index) && TEST_USERS[index]) {
        setUser(TEST_USERS[index]);
      }
    } else {
      // Fallback or default behavior
    }
  }, [searchParams]);

  return (
    <VeltProvider
      apiKey={process.env.NEXT_PUBLIC_VELT_API_KEY!}
      authProvider={{
        user: user,
      }}
    >
      <VeltIdentity>
        {/* <VeltCursor /> */}
        {children}
      </VeltIdentity>
    </VeltProvider>
  );
}

export function VeltSetup({ children }: { children: React.ReactNode }) {
  return (
    <Suspense fallback={null}>
      <VeltProviderInner>{children}</VeltProviderInner>
    </Suspense>
  );
}
```

Once identity is set, Velt automatically shows:

- Active users (avatars)
- Live cursor positions

You don’t have to manually sync cursor movement. Velt handles that internally based on user sessions.

### Canvas comments

[Comments](https://docs.velt.dev/async-collaboration/comments/overview) are one of the most important parts of a canvas app.

``` js
"use client";

import { useCommentAnnotations, VeltCommentPin } from "@veltdev/react";
import { Point } from "@/lib/types";
import { useWhiteboardStore } from "@/lib/useWhiteboardStore";

interface CanvasCommentLayerProps {
  zoom: number;
  pan: Point;
}
```

In [ CanvasCommentLayer.tsx](https://github.com/Studio1HQ/Velt-Demos/blob/main/excalidraw-velt-demo/components/velt/CanvasCommentLayer.tsx), comments are rendered as an overlay on top of the canvas. Instead of attaching comments to DOM elements, we attach them to canvas coordinates.

```
 {
              // Use bounds or specific fields
              // Normalized rect provided by normalizeRect helper? not available here easily.
              // Just use raw coords if available, or approximate.
              // Rect/Ellipse/Diamond have x1,y1,x2,y2 usually?
              // Wait, types.ts says DrawingElement...
              // Let's assume standard shape properties
              if (
                "x1" in element &&
                "y1" in element &&
                "x2" in element &&
                "y2" in element
              ) {
                worldX = (element.x1 + element.x2) / 2;
                worldY = (element.y1 + element.y2) / 2;
              }
            } else if (element.type === "line" || element.type === "arrow") {
              worldX = (element.x1 + element.x2) / 2;
              worldY = (element.y1 + element.y2) / 2;
            }
          }
        }

        if (typeof worldX !== "number" || typeof worldY !== "number") {
          return null;
        }

        const screenX = (worldX + pan.x) * zoom;
        const screenY = (worldY + pan.y) * zoom;

        return (
          <div
            key={annotation.annotationId}
            style={{
              position: "absolute",
              left: `${screenX}px`,
              top: `${screenY}px`,
              transform: "translate(-50%, -100%)",
              zIndex: 50,
              pointerEvents: "auto",
            }}
          >
            <VeltCommentPin annotationId={annotation.annotationId} />
          </div>
        );
      })}
    </div>
  );
```

From `app[/page.tsx](https://github.com/Studio1HQ/Velt-Demos/blob/main/excalidraw-velt-demo/app/page.tsx)`

, you trigger comments like this:

- Capture the
`(x, y)`

position on click - Pass context to Velt
- Optionally attach to a specific element using
`elementId`

This enables:

- Freeform comments anywhere on the canvas
- Context-aware discussions linked to shapes

This is very similar to how tools like Miro or Figma handle comments.

### Sidebar and UI controls

These files handle user-facing UI around collaboration.

-
`ProfileMenu.tsx`

shows user identity and active participants -
`ThemeToggle.tsx`

syncs your app theme with Velt UI

`ProfileMenu.tsx`

``` python
"use client";

import React, { useEffect, useState } from "react";
import { ChevronDown, User, Check, LogOut, RefreshCwIcon } from "lucide-react";
// import { useVeltClient } from "@veltdev/react";
import { TEST_USERS } from "@/lib/users";

export function ProfileMenu() {
  const [isOpen, setIsOpen] = useState(false);
  const [currentUser, setCurrentUser] = useState(TEST_USERS[0]); // Default to first user

  // Initialize from URL on mount
  useEffect(() => {
    const params = new 
