Figma

Comprehensive database schema for a Figma-like collaborative design platform with organization and team management, design files, page and frame hierarchy, component libraries, version history, comment threads with reactions, file sharing and permissions, and plugin ecosystem.

PostgreSQL15 tablesWeb App
designcollaborationprototypingdeveloper-tools
Created with ERD Studio

About this schema

The containment runs five levels deep: organization, team, project, file, page, and then frames inside the page. Each level exists because something attaches to it — billing to the organization, membership to teams, sharing to files, layout to frames.

comments carry x and y. A comment is pinned to a point on the canvas, not to an element, which is why a comment survives deleting the thing it was about and why it drifts when the design moves underneath it. That is a deliberate choice and its consequence is visible in every design tool that makes it.

frames.parent_id nests frames inside frames with absolute coordinates on each. A group is a frame whose children point at it, and moving the parent does not rewrite the children — the renderer composes positions, the database does not.

Key relationships

organizations → teams → projects → files
Four levels of containment, each owning something different: the plan, the membership, the grouping, the document.
files → users (twice)
creator_id and last_editor_id. Who made it and who touched it last answer different questions, and the second changes constantly.
frames → frames
parent_id with x, y, width and height on every row. Nesting is structure; position is absolute and composed at render time.
components → frames
A component points at the frame that defines it. The definition lives on the canvas rather than in a separate shape, which is what lets a designer make any frame a component.
files → shares
link_token with a permission and an expiry. Revoking a link is deleting a row, not changing the file.

Design decisions

Comments are pinned to coordinates

comments.x and comments.y anchor a thread to a position rather than to a frame id. Anchoring to the element would keep comments attached when a design moves, and would delete the conversation when the element goes — which is worse, because the discussion usually outlives the rectangle it was about. Coordinates mean a comment can end up pointing at empty canvas, and every tool that makes this choice lives with that.

Membership exists at two levels

org_members and team_members both exist because they answer different questions: the organization decides who is billed and who may create teams, the team decides who sees which projects. Collapsing them would mean either everyone in the org sees everything, or every team membership has to be granted individually to people who already belong.

Versions are named, not automatic

file_versions holds labelled snapshots created by a person, not an entry per edit. Design files change continuously and an entry per change is noise with a storage bill. The named version is the one someone wants to come back to, which is the only kind worth keeping forever.

Frame positions are absolute

Each frame stores its own x and y rather than an offset from its parent. Moving a group then updates one row instead of every descendant, and a frame's position can be read without walking up the tree. The cost is that the two numbers mean nothing until you know which page they are on.

Tables in this template

usersUsers
7 cols
organizationsOrganizations
7 cols
org_membersOrg Members
6 cols
teamsTeams
7 cols
team_membersTeam Members
6 cols
projectsProjects
6 cols
filesFiles
9 cols
file_versionsFile Versions
6 cols
pagesPages
6 cols
framesFrames
11 cols
componentsComponents
7 cols
commentsComments
10 cols
comment_reactionsComment Reactions
5 cols
sharesShares
7 cols
pluginsPlugins
9 cols

Frequently asked

Why do comments store coordinates instead of pointing at a frame?
So the conversation survives the element. Pointing at a frame would delete the thread when the frame goes, and discussions usually outlast the rectangle they were about. The trade is that a comment can end up on empty canvas after a redesign.
Why both org_members and team_members?
The organization decides billing and who may create teams; the team decides who sees which projects. One table would force either org-wide visibility or re-granting access to people who already belong.
Why does a component point at a frame?
Because any frame can become a component. The definition stays on the canvas where it was drawn rather than being copied into a separate shape, so editing the frame edits the component.
Where is the actual design data?
Not here. frames hold structure and geometry; the vector content, fills and effects belong in a document format the renderer reads. A relational row per path would be slower than the file it replaced.
How would multiplayer editing fit?
It would not go in these tables. Live cursors and operations are ephemeral and belong in a realtime layer; this schema is the durable state that layer checkpoints into, which is why file_versions is named snapshots rather than an operation log.