Codapult
FeaturesPricingAPIHelpChangelog
Codapult

Ship Your SaaS Faster

Product

  • Features
  • Pricing
  • Plugins
  • API Reference
  • Help Center
  • Feature Requests
  • Changelog

Company

  • Contact
  • GitHub

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Codapult. All rights reserved.

All articles

Getting Started

  • Introduction
  • Quick Start
  • Project Structure

Configuration

  • Environment Variables
  • App Configuration

Authentication

  • Authentication

Database

  • Database

Teams

  • Teams & Organizations

Payments

  • Payments & Billing

Api

  • API Layer

Ai

  • AI Features

Email

  • Email

Infrastructure

  • Infrastructure

Ui

  • UI & Theming

I18n

  • Internationalization

Content Management

  • Content Management

Admin

  • Admin Panel

Security

  • Security

Monitoring

  • Analytics & Monitoring

Modules

  • Module Architecture

Plugins

  • Plugin System

Deployment

  • Deployment
  • Troubleshooting

Upgrading

  • Upgrading Codapult

Developer Tools

  • MCP Server
  • Testing
Teams

Teams & Organizations

Multi-tenancy with organizations, roles, permissions, and team invitations.

Overview

Codapult provides built-in multi-tenancy through organizations. Users can create teams, invite members, assign roles, and manage settings — all with fine-grained role-based access control (RBAC).

Roles & Hierarchy

Every organization member has one of four roles, ordered from most to least privileged:

RoleLevelCapabilities
Owner4Full control — delete org, manage billing, all actions
Admin3Manage members, settings, billing, resources
Member2Create and edit resources, view billing
Viewer1Read-only access to org resources and settings

Global admins (user.role === 'admin') bypass all organization-level permission checks.

Permissions

Permissions are defined in src/lib/permissions.ts as a map of action to allowed roles:

const permissions = {
  'org:update': ['owner', 'admin'],
  'org:delete': ['owner'],
  'member:invite': ['owner', 'admin'],
  'member:remove': ['owner', 'admin'],
  'member:update-role': ['owner', 'admin'],
  'member:list': ['owner', 'admin', 'member', 'viewer'],
  'billing:manage': ['owner', 'admin'],
  'billing:view': ['owner', 'admin', 'member'],
  'resource:create': ['owner', 'admin', 'member'],
  'resource:read': ['owner', 'admin', 'member', 'viewer'],
  'resource:update': ['owner', 'admin', 'member'],
  'resource:delete': ['owner', 'admin'],
  'settings:manage': ['owner', 'admin'],
  'invitation:create': ['owner', 'admin'],
  'invitation:revoke': ['owner', 'admin'],
} satisfies Record<string, readonly OrgRole[]>;

Use the helper functions to check permissions in your code:

import { hasPermission, isRoleAtLeast, canModifyRole } from '@/lib/permissions';

hasPermission('member', 'resource:create'); // true
hasPermission('viewer', 'resource:create'); // false
isRoleAtLeast('admin', 'member'); // true
canModifyRole('admin', 'owner'); // false (must be strictly higher)

Guards

Server actions and API routes use guards from src/lib/guards.ts to enforce access control:

GuardChecks
requireAuth()User is logged in — throws if not
requireOrgMembership()User is a member of the organization
requireOrgAdmin()User is an owner or admin of the organization
requireOrgPermission()User has a specific permission in the organization

Usage in Server Actions

'use server';

import { requireOrgPermission } from '@/lib/guards';

export async function updateOrgSettings(orgId: string, data: unknown) {
  const { session, role } = await requireOrgPermission(orgId, 'settings:manage');

  // Only owners and admins reach this point
  // ... update settings ...
}

Usage in API Routes

import { requireOrgMembership } from '@/lib/guards';

export async function GET(req: Request) {
  const orgId = req.headers.get('x-org-id');
  if (!orgId) return NextResponse.json({ error: 'Missing org' }, { status: 400 });

  const { session } = await requireOrgMembership(orgId);
  // ... return org data ...
}

Invitations

Organization owners and admins can invite new members by email:

  1. Admin sends an invitation from Dashboard → Team → Invite Member
  2. An email with a unique token is sent to the invitee
  3. The invitee clicks the link and lands on /invite/[token]
  4. After accepting, they become a member with the assigned role

Invitations have an expiration date. Expired invitations cannot be accepted — the admin must resend.

Invitation Statuses

StatusDescription
pendingSent, awaiting acceptance
acceptedInvitee joined the organization
expiredToken expired before acceptance

Database Tables

Three tables power the teams module:

organization

ColumnTypeDescription
idtext (PK)Unique ID (nanoid)
nametextDisplay name
slugtextURL-safe unique identifier
imagetextAvatar URL (optional)
brandingtextJSON-encoded theme overrides
created_attimestampCreation date
updated_attimestampLast modified date

organization_member

ColumnTypeDescription
idtext (PK)Unique ID
organization_idtext (FK)References organization.id
user_idtext (FK)References user.id
roletextowner, admin, member, viewer
created_attimestampJoined date

organization_invitation

ColumnTypeDescription
idtext (PK)Unique ID
organization_idtext (FK)References organization.id
emailtextInvitee email address
roletextAssigned role on acceptance
tokentextUnique invitation token
statustextpending, accepted, expired
invited_bytext (FK)References user.id
expires_attimestampExpiration date
created_attimestampInvitation sent date

Server Actions

All team mutations are in src/lib/actions/organizations.ts:

ActionDescriptionRequired Permission
Create organizationCreates a new teamAuthenticated user
Update organizationUpdates name, avatar, brandingorg:update
Delete organizationPermanently deletes the teamorg:delete
Invite memberSends an email invitationmember:invite
Change member rolePromotes or demotes a membermember:update-role
Remove memberRemoves a member from the teammember:remove

UI Components

  • TeamSwitcher — dropdown in the dashboard sidebar for switching between organizations
  • Team settings page — manage name, avatar, and billing at Dashboard → Settings → Team
  • Members list — view, invite, and manage members at Dashboard → Team

Module Removal

The teams module is independently removable. See the Modules documentation for step-by-step removal instructions.

Next Steps

  • Database — schema conventions and query patterns
  • Payments & Billing — subscription management tied to organizations
  • Authentication — user accounts and sign-in methods
DatabasePayments & Billing