Draw entity–relationship diagrams in the browser: Chen notation or crow’s-foot tables, keys and relationships included. Share a live room or download a diagram. No account.
Diagrams save in your browser. Download a JSON backup (Ctrl+S) before clearing browser data.
- Draw — use Add your first entity, or open an example: Customer–Order, Student–Course, Employee–Dependent. Each opens in its own room.
- Name — new shapes take typing immediately. Double-click later to edit.
- Share or download — a room link, or SVG, PNG, JSON, or SQL.
⚙️ For developers: run locally
npm install
npm run dev
npm run verifyVerification runs unit tests, TypeScript, a production build, and browser tests. Browser tests start Vite preview and a local y-webrtc signaling server, with no public dependency. Rooms save through IndexedDB.
- Build panel adds entities, attributes, relationships, specializations.
- Connect in Chen: drag entity to entity. It creates the diamond with both participants. Drag onto a diamond to add a participant.
- Connect in crow’s-foot: pick two attribute rows. Change an endpoint via Connection attribute; Automatic follows the inferred key and foreign key.
- Check diagram flags missing names and keys, stray attributes, incomplete relationships, and invalid ranges. A hint opens the affected properties.
- Fit frames the drawing. Add unary relationship in Properties links an entity to itself.
- Entities come as Regular, Weak, or Associative.
- A crow’s-foot row with no attribute behind it was generated by the diagram. It stays editable and takes your name.
⚙️ For developers: exact-schema details
- Unique is a candidate key: it identifies a row but is not the primary key, and another table may reference it instead. Marked
UQin tables, broken underline in Chen,UNIQUEin SQL. Key instead makes it part of the primary key, and a foreign key must then carry every part. A generated primary-key role leaves an existingideditable. - Composite attributes own parts via Add attribute or Belongs to. Chen draws the hierarchy; tables and SQL use leaf names like
billing_address_street, keeping each leaf’s key and type. No parts means a scalar column. Deleting one removes its subtree; Undo restores it. - Hand-shaped lines use dbdiagram-style controls: round handle or section drags perpendicular, square splits at midpoint, Enter adds a section, arrows nudge (Shift larger), Escape cancels, Route line automatically restores routing. Neighbouring sections snap together; self-intersections are blocked, endpoint stubs protected. Segment positions sync live and survive reloads.
- Table positions sync separately from Chen positions; auto-arrange never overrules a hand move, and the newest placement wins a conflict.
- Specializations and attributes on relationships live in Diagram; selecting one switches to Chen.
- Scroll zooms toward the pointer; trackpad and two-finger pinch zoom too. Range: 25% to 300%.
- Left controls: Build, Diagram, Properties, Help. On phones they move to the bottom bar.
- Focus view (I) hides controls; Escape or I returns.
- Right-click canvas or a shape for its menu. Shift-click multi-selects. Shift-drag box-selects.
- Undo and Redo apply to edits; remote changes stay shared.
⚙️ For developers: phone and menu specifics
Editing sheets take at most 45% of the screen, keep normal scrolling, and stay above the on-screen keyboard; Done returns to the drawing while Select, Connect, Pan, Undo, and Redo stay reachable. Right-click menus also open with Shift+F10 or the Menu key: arrows navigate, Enter activates, Escape or Tab closes. Right-clicking a selected shape keeps the selection.
| Chen symbol | Meaning |
|---|---|
| Rectangle | Entity |
| Ellipse | Attribute |
| Diamond | Relationship |
| Triangle | Specialization |
| Double rectangle | Weak entity |
| Double diamond | Identifying relationship |
| Double ellipse | Multivalued attribute |
| Dashed ellipse | Derived attribute |
| Underlined attribute | Key |
A weak entity’s partial key identifies it within its parent; SQL combines it with the parent’s key.
⚙️ For developers: endpoint symbols and ranges
Chen endpoints follow ERDPlus: two bars required one, circle and bar optional one, bar and fork required many, circle and fork optional many. N and M both use the fork. Click an endpoint to edit constraints; advanced numeric ranges stay visible beside the line. Minimum / maximum takes whole numbers and N; invalid input stays local until corrected, and relationships may stay incomplete or exceed two participants.
Each person picks their own notation, stored per room and browser. JSON files and snapshots keep the exporter’s view field and current notation.
- New diagrams open in new rooms. Browser Back returns with the work saved.
- Share gives a short room link. The room is kept on the sync server, so the link opens the diagram even when nobody else is online; while it loads you may see Waiting for the diagram and the room code.
- Include diagram snapshot puts the diagram in the link itself: longer, and it opens with no server at all. It starts ticked when the app has no sync server to keep rooms. Both link types allow editing.
- Save a copy writes JSON that needs no server and survives chat apps. Import replaces the room you are in; Undo brings the old diagram back, and an invalid file leaves the room intact.
- Your name is asked on an empty diagram, stored per browser, and sent to peers as you type.
- A diagram lives in its room, in this browser, and — when a sync server is set up — on that server, so a link still opens later.
The header names the connection state:
| Status | Meaning |
|---|---|
| Live | Edits flow between browsers |
| Same browser | Other people are in this browser only |
| Waiting | The room is reachable, nobody else is in it |
| Offline | The signaling server is unreachable |
| Blocked | A peer was found; the network refused the connection |
A peer’s cursor appears only in the same notation, since the two views place one schema at different coordinates. Closing a tab removes a cursor immediately.
⚙️ For developers: how connections work
Tabs in one browser sync over BroadcastChannel; separate browsers use a sync server when one is configured, and WebRTC with a signaling server otherwise. Presence comes from connected peers and is shown apart from browser storage. Offline and Blocked offer Share, where a snapshot link can be copied.
With VITE_SYNC_URL set, the app talks to Cloudflare's cloudflare/worker.js instead of the signaling servers; that worker writes a room to storage when the last person leaves, which is what lets a short link open with nobody online. Without it, default server: wss://signaling.fly.dev. Override with ?signaling=, window.__ERD_SIGNALING__, or VITE_SIGNALING. If direct connections are blocked, editing and snapshot links still work; cross-device live edits do not arrive.
SVG, PNG, JSON, SQL. Image exports use the full drawing bounds and strip selection, hover, connection, and presence overlays.
⚙️ For developers: SQL export rules
The SQL tab offers a dialect: Standard SQL, PostgreSQL, MySQL, or SQLite. It changes only generated key spelling (INTEGER, SERIAL, INT AUTO_INCREMENT), stored per browser and exporter, not per diagram.
Mapping: entities become tables, weak entities borrow the parent key, many-to-many makes junction tables, one-to-many adds foreign keys. Connection attributes supply the actual columns; explicit one-to-one enforces uniqueness. The export explains a reference that needs a single-column primary key or an endpoint that is gone. Foreign-key types follow the referenced key; other types use the chosen type or infer from the name.
Types are a base plus numbers: length for VARCHAR, precision and scale for DECIMAL, so VARCHAR(64) and NUMERIC(12,4) instead of fixed presets. Bases: integers, text, character strings, numeric, boolean, date/time, UUID, JSON, bytea, enum. ENUM takes a comma-separated list, shown as SQL (ENUM('pending','active','closed')); quotes are optional on input, added on export, and escaped. Bad numbers such as VARCHAR(0) are refused, not repaired. Types persist through edits, collaboration, JSON, and snapshots. SQL/DBML import is out of scope.
| Key | Action |
|---|---|
| E / A / R / S | Add entity / attribute / relationship / specialization |
| V / C / H | Select / Connect / Pan |
| F | Fit diagram |
| I | Toggle Focus view |
| Shift-click | Add to or remove from the selection |
| Double-click | Reveal the shape's properties |
| Shift-drag empty canvas | Select several shapes with a box |
| Shift+F10 / Menu key | Open canvas actions |
| Escape | Finish typing, then cancel selection or connection |
| Delete | Delete selection |
| Ctrl or ⌘ + Z | Undo |
| Ctrl or ⌘ + Shift + Z | Redo |
| Ctrl or ⌘ + S | Download JSON |
| ? | Open Help |
IBM Plex type, paper-and-ink surfaces, quiet drafting grids, teal selection. Follows system light or dark and reduced motion. Help adds Solarized Light, Solarized Dark, Dracula, Nord, GitHub Dark, and Monokai using each project’s published values. Themes change colour tokens only, all at 4.5:1 body contrast. The choice is stored per browser; System clears it.
⚙️ For developers: design references
The thesis, palette, geometry rules, and accessibility floor live in DESIGN.md, held by two unit tests: every var() a rule reads is set somewhere, and every theme defines its tokens at 4.5:1. Read it before changing a token or adding a surface. A two-entity keyed schema stays within 11 keyboard interactions.
Teaching reference: ERDPlus. Editing and examples informed by drawDB, keyboard efficiency by dbdiagram’s guide, peer state by DrawSQL. The React/CSS/SVG/Yjs implementation and serialized model stay as they are.