Excalidraw is an open-source virtual whiteboard for hand-drawn-style diagrams, shipped both as an embeddable React component on npm and as the local-first, collaborative excalidraw.com web app.
- Language
- TypeScript
- Hosting
- Vercel
- License
- MIT
- Running for
- 5 years
- Team
- 2-5 people
Why this architecture
A client-only canvas editor kept free of backend dependencies so it can be embedded anywhere; sharing, live collaboration and persistence are layered on in the app shell with Firebase and a Socket.IO relay.
Tech stack by layer
11 technologies · audited Sep 16, 2026- ReactUI shell, toolbars and menus; the drawing surface itself is rendered imperatively on a canvas.
- TypeScriptEvery workspace package is TypeScript; the root `test:typecheck` script runs `tsc` across the monorepo.
- Canvas APIInfinite whiteboard drawn on HTML5 canvas; `canvas-roundrect-polyfill` and `points-on-curve` support the renderer.
- Rough.jsGenerates the hand-drawn look for every shape; `perfect-freehand` handles free-draw strokes.
- JotaiAtom-based state for the editor package and the app shell (`jotai`, `jotai-scope`, `excalidraw-app/app-jotai.ts`).
- Radix UIAccessible primitives behind menus and dialogs inside the editor package.
- ViteBuilds the excalidraw.com app (`vite.config.mts`); `vite-plugin-pwa` produces the offline-capable PWA.
- Socket.IOClient of a separate Socket.IO relay for live collaboration; the relay server is not part of this repository.
- VercelHosts excalidraw.com; `vercel.json` sets `outputDirectory` to `excalidraw-app/build` and the build reads `VERCEL_GIT_COMMIT_SHA`.
- DockerMulti-stage image builds the app on Node 24 and serves the static bundle from nginx; published by `publish-docker.yml`.
- SentryBrowser error tracking in the app shell (`@sentry/browser`, `excalidraw-app/sentry.ts`).
- VitestUnit and snapshot tests with canvas mocking (`vitest-canvas-mock`) and coverage in CI.
- GitHub ActionsLint, test, size-limit, Docker publish, Sentry release and npm autorelease workflows.
- ESLintShared `@excalidraw/eslint-config` and Prettier config enforced through husky and lint-staged.
Excalidraw architecture diagram
Open SVGDiagram as text
- excalidraw.com (Vite · PWA) → @excalidraw/excalidraw (React · canvas): embeds editor
- @excalidraw/excalidraw (React · canvas) → IndexedDB (local drawing): autosave
- excalidraw.com (Vite · PWA) → Collab relay (Socket.IO): live rooms
- excalidraw.com (Vite · PWA) → Firebase (shared scenes · files): share links
- Vercel (static hosting) → excalidraw.com (Vite · PWA): hosts
Key architectural decisions
5 decisions- 01
The editor is an npm package; excalidraw.com is a thin app around it
The yarn workspace splits
packages/excalidraw(published as@excalidraw/excalidraw, a React component) fromexcalidraw-app(the excalidraw.com shell). Collaboration, sharing, analytics and persistence live only in the app, so the embeddable package stays free of backend dependencies. - 02
Rendering is imperative canvas, not DOM
Shapes are drawn on an HTML5 canvas with Rough.js for the hand-drawn look and perfect-freehand for pen strokes. React only owns the chrome around the canvas, which keeps the editor responsive on large scenes and lets the package expose its own scene model instead of DOM nodes.
- 03
Local-first by default, backend only for sharing
The app autosaves to the browser (
idb-keyval) and works offline as a PWA (vite-plugin-pwa). Firebase Firestore and Storage are used only when a user shares a link or starts a live session, and the README describes those flows as end-to-end encrypted. - 04
Collaboration is a relay, not a server-side document model
Live sessions use
socket.io-clientto broadcast element updates through a relay; the relay server is not in this repository. Ordering of elements relies on the vendored@excalidraw/fractional-indexingpackage, which lets clients reconcile concurrent edits without a central authority. - 05
Small internal packages with an explicit build pipeline
packages/common,element,math,utils,laser-pointerandfractional-indexingare separate workspace packages, each built to ESM by scripts inscripts/(buildBase.js,buildPackage.js). This keeps the public API surface of the editor package deliberate.
How Excalidraw is built
How Excalidraw is structured
The repository is a single Yarn 1 workspace (packageManager: yarn@1.22.22) with three groups of packages declared in the root package.json: excalidraw-app, packages/* and examples/*.
packages/excalidrawis the product most people install:@excalidraw/excalidraw, "Excalidraw as a React component". It declares React 17 to 19 as a peer dependency and pulls in the drawing and UI libraries (Rough.js, perfect-freehand, Radix UI, CodeMirror for text editing,pakofor compressed exports,browser-fs-accessfor the file system API).packages/common,packages/element,packages/math,packages/utilsandpackages/laser-pointerare small internal packages that the editor composes.packages/fractional-indexingis a vendored copy of the CC0-licensed ordering library used to keep element order stable across concurrent edits.excalidraw-appis the excalidraw.com shell. It is the only place that knows about Firebase, Socket.IO, Sentry and analytics; the rootbuildscript simply delegates toyarn --cwd ./excalidraw-app build.examples/with-nextjsandexamples/with-script-in-browsershow how the package is consumed from Next.js and from a plain<script>tag, anddev-docsis a Docusaurus site (which is why MDX shows up as the third language in the repository).
The split is the central architectural idea: everything that would make the editor hard to embed (network, storage, accounts) is kept out of the package and put in the app.
Frontend
Excalidraw does not render shapes as DOM nodes. The scene is drawn onto an HTML5 canvas, with Rough.js producing the sketchy strokes and perfect-freehand producing pen input. React 19 owns the chrome around the canvas: toolbars, menus, dialogs and the properties panel, built on Radix UI primitives. State is managed with Jotai atoms; the app keeps its own store in excalidraw-app/app-jotai.ts, and the package uses jotai-scope so multiple editors can coexist on a page.
Styling is SCSS (excalidraw-app/index.scss, sass in the package dependencies), and localisation is handled with i18next plus a Crowdin workflow (crowdin.yml, locales-coverage.yml). The app is built with Vite 5 (excalidraw-app/vite.config.mts), using vite-plugin-pwa for the service worker and vite-plugin-sitemap and vite-plugin-html for the public site.
Backend & APIs
There is no application server in this repository. The only network client for collaboration is socket.io-client in excalidraw-app/package.json; the matching relay server lives outside this repo. The excalidraw-app/collab and excalidraw-app/share directories hold the collaboration and share-link flows, and excalidraw-app/data holds the persistence adapters.
Data & persistence
Persistence is local-first. The current drawing is autosaved to the browser with idb-keyval (IndexedDB), and the PWA service worker lets the app open offline. When a user creates a shareable link or joins a live room, scene data and uploaded files go to Firebase: the firebase-project/ directory contains firestore.rules, firestore.indexes.json and storage.rules, so both Firestore and Firebase Storage are in use. The README states that both sharing and live collaboration are end-to-end encrypted, meaning the backend stores ciphertext produced in the browser.
Realtime / collaboration
Live sessions broadcast element updates through the Socket.IO relay rather than through a server-side document model. Each client reconciles incoming changes locally; stable ordering comes from fractional indexing, which is why that library is vendored as its own workspace package. Presence features such as the laser pointer are implemented in packages/laser-pointer.
Build, test & deploy
- Tests run on Vitest 3 with
vitest-canvas-mockand jsdom (vitest.config.mts,setupTests.ts);test-coverage-pr.ymlreports coverage on pull requests andsize-limit.ymlguards the bundle size of the package. test:typecheckrunstscfor the whole workspace on TypeScript 5.9; ESLint and Prettier run through husky and lint-staged on commit.- excalidraw.com deploys on Vercel:
vercel.jsonsetsoutputDirectorytoexcalidraw-app/build, and thebuild:appscript readsVERCEL_GIT_COMMIT_SHAto stamp the release.sentry-production.ymluploads release information to Sentry. autorelease-excalidraw.ymlpublishes the npm package;build-docker.ymlandpublish-docker.ymlbuild and push the container image.
Self-hosting notes
The Dockerfile is a two-stage build: a node:24 stage runs yarn build:app:docker (which disables Sentry), then an nginx:stable-alpine-slim stage serves excalidraw-app/build as static files with a health check. docker-compose.yml runs the same image in development mode on port 3000. Because collaboration and sharing depend on external services, a self-hosted instance works as a standalone whiteboard out of the box; live rooms require running a compatible relay and configuring the Firebase and collaboration endpoints through the app's environment variables (.env.development and .env.production list the variables).
What to copy (and what not to)
Copy the package-versus-app split if you are building an editor you want others to embed: it forces network and storage concerns into a thin outer layer. Copy the local-first default and the "backend only when sharing" posture for tools where privacy is a feature. Be careful copying the relay-based collaboration model as-is; it works because Excalidraw's scene model tolerates last-writer-wins on individual elements with fractional ordering, which is not true of every document type.
Sources & repo audit
- Root package.json (yarn workspaces, Vite, Vitest, vite-plugin-pwa)
- excalidraw-app/package.json (firebase, socket.io-client, jotai, idb-keyval, sentry)
- packages/excalidraw/package.json (roughjs, perfect-freehand, radix-ui, codemirror)
- firebase-project/ (firestore.rules, storage.rules)
- Dockerfile (Node 24 build, nginx runtime) and vercel.json
Independent analysis of repository at github.com/excalidraw/excalidraw. Spotted an inaccuracy? Use the claim form to request a correction.
Show this stack on your README or website
[](https://stackitfast.com/project/excalidraw)Paste it into your README. It shows your current stack and links to this page.
Scaffold it with your agent
Paste this prompt into Claude Code, Cursor, Windsurf or AGY to start a project with Excalidraw's architecture.
- 1Copy the promptThe full markdown spec, with every layer and decision.
- 2Open your AI toolClaude Code, Cursor, Windsurf or Copilot, in a new repo.
- 3Paste and scaffoldUse it as the first instruction; review before you ship.
# MISSION: Scaffold "Excalidraw" Production Architecture
You are an expert Senior Staff Software Architect and Full-Stack Engineer. Your mission is to scaffold and implement a production-grade, highly reliable, and modular codebase following the proven architecture of **Excalidraw**.
---
## 1. PROJECT SPECIFICATIONS & BENCHMARK
- **Reference Architecture**: Excalidraw
- **What It Does**: Excalidraw is an open-source virtual whiteboard for hand-drawn-style diagrams, shipped both as an embeddable React component on npm and as the local-first, collaborative excalidraw.com web app.
- **Domain & Category**: Canvas Editor
- **Production Scale**: 2-5 people
- **Development Mode**: CLASSIC
- **Architectural Rationale**: A client-only canvas editor kept free of backend dependencies so it can be embedded anywhere; sharing, live collaboration and persistence are layered on in the app shell with Firebase and a Socket.IO relay.
- **Live Website Reference**: https://excalidraw.com
- **Source Repository**: https://github.com/excalidraw/excalidraw
---
## 2. PRODUCTION TECH STACK
- **Full Stack Array**: React, TypeScript, Vite, Canvas API, Rough.js, Jotai, Firebase, Socket.IO, Vitest, Vercel, Docker
- **Primary Language(s)**: TypeScript
- **License of the reference repo**: MIT
- **Frontend**: React — UI shell, toolbars and menus; the drawing surface itself is rendered imperatively on a canvas.; TypeScript — Every workspace package is TypeScript; the root `test:typecheck` script runs `tsc` across the monorepo.; Canvas API — Infinite whiteboard drawn on HTML5 canvas; `canvas-roundrect-polyfill` and `points-on-curve` support the renderer.; Rough.js — Generates the hand-drawn look for every shape; `perfect-freehand` handles free-draw strokes.; Jotai — Atom-based state for the editor package and the app shell (`jotai`, `jotai-scope`, `excalidraw-app/app-jotai.ts`).; Radix UI — Accessible primitives behind menus and dialogs inside the editor package.; Vite — Builds the excalidraw.com app (`vite.config.mts`); `vite-plugin-pwa` produces the offline-capable PWA.
- **Backend & APIs**: Socket.IO — Client of a separate Socket.IO relay for live collaboration; the relay server is not part of this repository.
- **Data & persistence**: Firebase — Firestore and Firebase Storage hold shared scenes and files for links and live rooms; rules live in `firebase-project/`.; IndexedDB — Local-first persistence of the current drawing through `idb-keyval`.
- **Infrastructure & deploy**: Vercel — Hosts excalidraw.com; `vercel.json` sets `outputDirectory` to `excalidraw-app/build` and the build reads `VERCEL_GIT_COMMIT_SHA`.; Docker — Multi-stage image builds the app on Node 24 and serves the static bundle from nginx; published by `publish-docker.yml`.; Sentry — Browser error tracking in the app shell (`@sentry/browser`, `excalidraw-app/sentry.ts`).
- **Tooling, testing & ops**: Vitest — Unit and snapshot tests with canvas mocking (`vitest-canvas-mock`) and coverage in CI.; GitHub Actions — Lint, test, size-limit, Docker publish, Sentry release and npm autorelease workflows.; ESLint — Shared `@excalidraw/eslint-config` and Prettier config enforced through husky and lint-staged.
---
## 3. KEY ARCHITECTURAL DECISIONS (audited from https://github.com/excalidraw/excalidraw @ c0ad61c)
1. **The editor is an npm package; excalidraw.com is a thin app around it**: The yarn workspace splits `packages/excalidraw` (published as `@excalidraw/excalidraw`, a React component) from `excalidraw-app` (the excalidraw.com shell). Collaboration, sharing, analytics and persistence live only in the app, so the embeddable package stays free of backend dependencies.
2. **Rendering is imperative canvas, not DOM**: Shapes are drawn on an HTML5 canvas with Rough.js for the hand-drawn look and perfect-freehand for pen strokes. React only owns the chrome around the canvas, which keeps the editor responsive on large scenes and lets the package expose its own scene model instead of DOM nodes.
3. **Local-first by default, backend only for sharing**: The app autosaves to the browser (`idb-keyval`) and works offline as a PWA (`vite-plugin-pwa`). Firebase Firestore and Storage are used only when a user shares a link or starts a live session, and the README describes those flows as end-to-end encrypted.
4. **Collaboration is a relay, not a server-side document model**: Live sessions use `socket.io-client` to broadcast element updates through a relay; the relay server is not in this repository. Ordering of elements relies on the vendored `@excalidraw/fractional-indexing` package, which lets clients reconcile concurrent edits without a central authority.
5. **Small internal packages with an explicit build pipeline**: `packages/common`, `element`, `math`, `utils`, `laser-pointer` and `fractional-indexing` are separate workspace packages, each built to ESM by scripts in `scripts/` (`buildBase.js`, `buildPackage.js`). This keeps the public API surface of the editor package deliberate.
---
## 4. NON-NEGOTIABLE ARCHITECTURAL GUARDRAILS
1. **Monorepo & Modular Separation**:
- Structure as a Turborepo monorepo with strict package boundaries:
- `apps/web`: Application UI, routing, layouts, and server endpoints.
- `packages/ui`: Shared design tokens, CSS variables, and Radix UI primitive components.
- `packages/db`: Database schemas, client singleton, declarative migrations, and seed scripts.
- `packages/config`: Shared TypeScript, ESLint, and build configurations.
2. **Strict Type Safety & Zero `any` Policy**:
- Enable `strict: true`, `noImplicitAny: true`, and `strictNullChecks: true`.
- Validate ALL external inputs, API request bodies, and query parameters with **Zod** schemas before execution.
3. **Frontend & Rendering Guidelines**:
- Utilize Vite 6 with TanStack Router for fully type-safe routing. Manage server state and caching via TanStack Query v5 with optimistic UI updates.
4. **Design System & Aesthetics**:
- Keep every color, radius, shadow and font in a single token file (CSS variables) and consume tokens everywhere; never hardcode hex values in components.
- Prefer crisp 1px borders and one subtle shadow scale over blurry default shadows. Pair one sans-serif for body/headings with one monospace for tags, badges, metrics, and code.
5. **Data Layer & Reliability**:
- Write declarative schema definitions with foreign keys, composite indexes on queried filters, and automated timestamp triggers.
- Use connection pooling and prepared statements for serverless database execution.
---
## 5. STEP-BY-STEP SCAFFOLDING ROADMAP
- **Phase 1: Workspace & Root Config**: Initialize package manager, monorepo configuration (`turbo.json`, `tsconfig.base.json`, `package.json`).
- **Phase 2: Database Schema & Client**: Set up the data layer (Firebase, IndexedDB): client, connection pool, models, and migration scripts.
- **Phase 3: Design Tokens & UI Primitives**: Build accessible `Button`, `Input`, `Card`, `Badge`, and layout wrappers inside `packages/ui`.
- **Phase 4: Core Application Routes & Handlers**: Implement primary authentication, user session handling, and application routes.
- **Phase 5: Quality Assurance & Build Verification**: Run `tsc --noEmit`, ESLint, Prettier, and smoke test suites to ensure zero compilation or runtime errors.
---
## 6. EXECUTION INSTRUCTIONS
1. Review all specifications, architectural guardrails, and stack choices above.
2. Present the full monorepo directory tree structure.
3. Systematically generate the complete, production-ready codebase according to the 5-phase roadmap above — starting with the root workspace setup, followed by the database schema, UI design system package, and full-stack application routes until the repository is fully scaffolded and ready to run.Copied 1 time
Frequently asked about Excalidraw
What is Excalidraw built with?
Excalidraw is written in TypeScript with React for the UI chrome and an HTML5 canvas for the drawing surface, using Rough.js for the hand-drawn style. The editor ships as the npm package @excalidraw/excalidraw; the excalidraw.com app around it is built with Vite and adds Firebase, Socket.IO and a PWA service worker.
Does Excalidraw need a backend?
No. The editor runs entirely in the browser and the app autosaves locally with IndexedDB. A backend is only involved for optional features: Firebase Firestore and Storage for shareable links, and a Socket.IO relay for live collaboration sessions.
How does real-time collaboration work in Excalidraw?
The app connects with socket.io-client to a relay server that broadcasts element updates between participants in a room. The relay is not part of this repository. Scene snapshots for a room are stored in Firebase, and the project describes both sharing and live sessions as end-to-end encrypted.
Can I self-host Excalidraw?
Yes. The repository ships a multi-stage Dockerfile that builds the app with Node and serves the static bundle from nginx, plus a docker-compose.yml for development. To enable collaboration you also need to run a compatible Socket.IO relay and point the app at it through its environment variables.
Can I embed Excalidraw in my own React app?
Yes. Install @excalidraw/excalidraw with React and React DOM as peer dependencies. The repository includes examples/with-nextjs and examples/with-script-in-browser showing both a Next.js integration and a plain script tag setup.
One email a month: new deep dives and stack trends
New source-audited architectures, head-to-head comparisons and the monthly stack report. No spam, unsubscribe anytime.
Similar architectures
- Next.jsHybridFullstack React Framework · 1M+ MAUShares TypeScript · React · Vercel
- NovuHybridNotification Infra · 6-20 peopleShares TypeScript · React · Socket.IO
- BrowserlessClassicHeadless Browser Automation Infrastructure · 2-5 PeopleShares TypeScript · Docker
- Supabase StudioClassicDeveloper Tool · 20+ peopleShares React · TypeScript · Docker
- tRPCHybridEnd-to-End Typesafe API Framework · 1M+ MAUShares TypeScript · React
- shadcn/uiHybridCopy-Paste UI Component Architecture · 1M+ MAUShares React · TypeScript · Vite