General
Organizations

Data Model

Learn how organizations are modeled in the monorepo.

Diagram

OrganizationidmembershipsPKMembershiporganizationIduserIdroleisOwnerFKFKUseridmembershipsPK10..n0..n1

Organization

The data model of an organization looks like the following (simplified):

packages/database/src/schema.ts
export const organizationTable = pgTable(
  'organization',
  {
    id: uuid('id').primaryKey().notNull().defaultRandom(),
    slug: varchar('slug', { length: 255 }).notNull()
    logo: varchar('logo', { length: 2048 }),
    name: varchar('name', { length: 255 }).notNull()
  }
);

export const organizationRelations = relations(
  organizationTable,
  ({ many }) => ({
    memberships: many(membershipTable)
  })
);

Notice the memberships relations? We don't directly connect an organization to users, but use an intermediate table.

Membership

Memberships allow a user to join multiple organizations. A membership looks like the following:

packages/database/src/schema.ts
export const membershipTable = pgTable('membership', {
  id: uuid('id').primaryKey().notNull().defaultRandom(),
  organizationId: uuid('organizationId')
    .notNull()
    .references(() => organizationTable.id, {
      onDelete: 'cascade',
      onUpdate: 'cascade'
    }),
  userId: uuid('userId')
    .notNull()
    .references(() => userTable.id, {
      onDelete: 'cascade',
      onUpdate: 'cascade'
    }),
  role: roleEnum('role').default(Role.MEMBER).notNull(),
  isOwner: boolean('isOwner').default(false).notNull()
});

export const membershipRelations = relations(membershipTable, ({ one }) => ({
  user: one(userTable, {
    fields: [membershipTable.userId],
    references: [userTable.id]
  }),
  organization: one(organizationTable, {
    fields: [membershipTable.organizationId],
    references: [organizationTable.id]
  })
});

Notice the role field? A user can be an admin in one organization and member in another one.

Roles

Roles are saved in the membership table. Following roles are available:

packages/database/src/schema.ts
export enum Role {
  MEMBER = 'member',
  ADMIN = 'admin'
}

You can of course define more roles, but we recommend to keep the amount of roles to a minimum.

Rename organizations

The starter kit uses "organizations" to represent a group of users, but this term may not align with your application's domain, even if the underlying data model is similar.

For instance, you might prefer to refer to them as "Teams" or "Workspaces." In this case, we recommend keeping "organization" as the technical term in your codebase (to avoid renaming every instance) and updating the labels in the UI. To implement this, simply update all instances of "Organization" to your preferred term.

Isolation level

All organizations are saved in the same database with organizationId as tenant separator. It's possible to create a database per organization with a global catalog database, but it requires some modification. In that case we can recommend multiple schemas instead of databases, since one database is always easier to maintain.