
Most SaaS codebases start user-first because every tutorial does: a User table,
a session, and rows that hang off userId. That shape works right up until your
first real B2B customer asks to invite a colleague. Then you discover that
"add teams later" was never a feature, it was a rewrite.
The migration nobody budgets for
Going from user-owned rows to org-owned rows touches everything at once:
- Every query that filtered by
userIdnow needs a tenant scope, and missing one is a data leak, not a bug. - Authorization stops being a boolean. The same person is an owner in one workspace and a viewer in another, so permissions become a property of membership, not of the user.
- Billing moves from the person to the organization, and Stripe customers, subscriptions and seat counts all have to be re-parented while invoices keep flowing.
Each of those is risky alone. Doing all three under a live product, with customers in the tables you are rewriting, is how a two week estimate becomes a quarter.
What org-first means in the schema
Org-first does not mean building enterprise features early. It means the tenant boundary exists from the first migration:
model Organization {
id String @id @default(cuid())
slug String @unique
members Member[]
}
model Member {
id String @id @default(cuid())
role String
userId String
organizationId String
user User @relation(fields: [userId], references: [id])
organization Organization @relation(fields: [organizationId], references: [id])
@@unique([userId, organizationId])
}
The user owns their identity. The membership owns the role. Everything the product creates belongs to the organization. A solo founder using your app is simply an organization with one member, which costs you nothing extra at runtime.

Where this bites hardest: billing
Seats, plan limits and upgrade prompts are all questions about an organization.
If subscriptions attach to users, "add a teammate" turns into a support
conversation about whose card is on file. Attach them to the organization and
the seat count is a Member count, the limit check is one query, and the
invoice reads the way your customer's finance team expects.
Start org-first even when you are solo
This is exactly the plumbing saasprokit ships finished: organizations, invitations, per-org roles alongside platform admin, and Stripe billing that was never user-shaped. If you build your own, build it in this order anyway. The org table is a day of work on an empty database and a quarter of work on a full one.