Multiplayer guide: from scratch to working whiteboard
Step-by-step: create a SvelteKit app, add Excalidraw multiplayer with file-system persistence, and run it.
1. Create the project
npx sv create my-whiteboard
cd my-whiteboard Pick minimal or demo, and TypeScript if you like.
2. Install svelte-excalidraw
npm install svelte-excalidraw 3. Enable Remote functions
Edit svelte.config.js and add:
kit: {
adapter: adapter(),
experimental: {
remoteFunctions: true
}
},
compilerOptions: {
experimental: {
async: true
}
} 4. Add the stream route
Create src/routes/room/[roomId]/+server.ts:
import { handleExcalidrawStream } from "svelte-excalidraw/server";
export const prerender = false;
export async function POST({ request, params }) {
const body = (await request.json().catch(() => ({}))) as {
username?: string;
color?: { background: string; stroke: string };
};
const roomId = params.roomId;
const username = body.username?.trim();
if (!roomId || !username)
return new Response("Missing roomId or username", { status: 400 });
return handleExcalidrawStream(request, {
roomId,
username,
color: body.color,
});
} Persistence is on by default: rooms are saved to data/excalidraw-rooms/{roomId}/.
5. Add the whiteboard page
Create src/routes/room/[roomId]/+page.svelte:
<script>
import { page } from "$app/state";
import { ExcalidrawMultiplayer, createDefaultAdapter } from "svelte-excalidraw";
const adapter = createDefaultAdapter({
streamUrl: (roomId) => `/room/${roomId}`,
});
</script>
<a href="/">← Back</a>
<ExcalidrawMultiplayer
roomId={page.params.roomId}
userInfo={{ username: "You", color: { background: "#6965db", stroke: "#6965db" } }}
{adapter}
/> 6. Add the home page
Edit src/routes/+page.svelte:
<script>
import { goto } from "$app/navigation";
let roomId = $state("");
function createRoom() {
goto(`/room/${crypto.randomUUID().replace(/-/g, "").slice(0, 8)}`);
}
function joinRoom() {
const id = roomId.trim();
if (id) goto(`/room/${encodeURIComponent(id)}`);
}
</script>
<h1>Whiteboard</h1>
<button onclick={createRoom}>Create room</button>
<div>
<input type="text" bind:value={roomId} placeholder="Room ID" onkeydown={(e) => e.key === "Enter" && joinRoom()} />
<button onclick={joinRoom}>Join</button>
</div> 7. Run it
npm run dev Open http://localhost:5173, create a room, open it in another tab or browser, and draw. Changes sync in real time and persist to data/excalidraw-rooms/ across restarts.