Projects Feature
Complete guide to the Projects feature - multi-tenant project management with roles, permissions, and settings
The Projects feature provides a comprehensive multi-tenant project management system where team accounts can create and manage multiple projects, each with its own members, roles, and permissions.
Overview
Projects are organizational units within a team account that allow you to:
- Organize work into separate workspaces
- Manage project-specific members with granular permissions
- Track project status and details independently
- Control access at both team and project levels
Architecture
Database Schema
The projects system uses the following core tables:
projects table
- id (uuid, primary key) - account_id (uuid, foreign key to accounts) - name (varchar 255) - Project display name - slug (varchar 128) - URL-friendly identifier - description (text) - Project description - picture_url (varchar 1000) - Project avatar/logo - active (boolean) - Project status - primary_owner_user_id (uuid) - Owner reference - created_at (timestamptz) - updated_at (timestamptz)
projects_members table
- id (bigserial, primary key) - user_id (uuid, foreign key to auth.users) - project_id (uuid, foreign key to projects) - role (enum: owner, admin, member, readonly) - created_at (timestamptz)
Row Level Security (RLS)
All project data is protected by PostgreSQL RLS policies:
- Users can only access projects they are members of
- Role hierarchy enforces permission levels
- Team account membership is validated automatically
Features
1. Project Creation
- Location: Team account dashboard sidebar
- Access: Team members with
projects.createpermission - UI Component:
CreateProjectDialog - Server Action:
createProjectAction
Workflow:
- User clicks "+ Add project" in sidebar
- Dialog form appears with name and description fields
- Slug is auto-generated from name (with manual override)
- Real-time slug uniqueness validation
- Project created with creator as owner
2. Project Settings
Full settings management accessible at /home/[account]/projects/[id]/settings
General Settings
- Project name (updates slug if not manually customized)
- URL slug (validated for uniqueness)
- Description (max 1000 characters)
- Status (active/inactive toggle)
- Project logo/avatar upload
Danger Zone
- Delete project (owner only)
- Requires confirmation
- Cascades to project members and related data
3. Members Management
Comprehensive member management at /home/[account]/projects/[id]/members
Features:
- Add Members: Invite team account members to project
- Roles: Owner, Admin, Member, Readonly
- Permissions: Role-based access control
- Remove Members: Owner/Admin can remove members
- Update Roles: Change member roles (respects hierarchy)
Role Hierarchy:
- Owner (hierarchy level: 100)
- Full project control
- Can delete project
- Can manage all members
- Admin (hierarchy level: 50)
- Manage members (except owners)
- Edit project settings
- Cannot delete project
- Member (hierarchy level: 25)
- View and edit project content
- Cannot manage members or settings
- Readonly (hierarchy level: 10)
- View-only access
- Cannot make changes
File Organization
packages/features/projects/ ├── src/ │ ├── components/ │ │ └── settings/ │ │ ├── project-settings-container.tsx │ │ ├── update-project-details-form.tsx │ │ ├── update-project-image-container.tsx │ │ └── project-danger-zone.tsx │ ├── server/ │ │ └── services/ │ │ └── projects.service.ts │ └── schema/ │ └── project.schema.ts │ apps/web/app/home/[account]/projects/ ├── [id]/ │ ├── settings/ │ │ ├── page.tsx │ │ ├── _components/ │ │ └── _lib/server/ │ └── members/ │ ├── page.tsx │ └── _components/ └── _components/ └── create-project-dialog.tsx
Key Components
CreateProjectDialog
Purpose: Modal form for creating new projects Features:
- Auto-slug generation from name
- Real-time uniqueness validation
- Form validation with Zod
- Error handling with toast notifications
ProjectSettingsContainer
Purpose: Main container for project settings Sections:
- Project logo uploader
- Project details form
- Danger zone (delete project)
UpdateProjectDetailsForm
Purpose: Form for editing project information Features:
- Smart slug auto-generation (preserves manual edits)
- Debounced validation (1 second)
- Visual feedback (loading, success, error icons)
- Status toggle (active/inactive)
Use Cases
Use Case 1: Creating a Project
Actor: Team Member with projects.create permission Precondition: User is logged in and part of a team account
Flow:
- Navigate to team dashboard
- Click "+ Add project" in sidebar
- Enter project name: "Website Redesign"
- Slug auto-generates: "website-redesign"
- (Optional) Add description
- Click "Create Project"
- System validates slug uniqueness
- Project created with user as owner
- User redirected to project page
Use Case 2: Updating Project Slug Independently
Actor: Project Owner Precondition: User is project owner
Flow:
- Navigate to project settings
- See current name: "Q4 Campaign"
- Current slug: "q4-campaign"
- User wants custom slug without changing name
- Manually edit slug to: "2024-q4-campaign"
- System validates uniqueness (shows checkmark)
- Click "Save Settings"
- Only slug updates, name unchanged
- User redirected to new URL with updated slug
Use Case 3: Adding Project Member
Actor: Project Admin Precondition: User has admin or owner role in project
Flow:
- Navigate to project members page
- Click "Add Member" button
- Select team member from dropdown
- Choose role (e.g., "Member")
- Click "Add"
- Member receives notification (if enabled)
- Member appears in project members list
- Member can now access project
API Reference
Server Actions
createProjectAction
createProjectAction({
name: string,
description: string | null,
accountSlug: string,
accountId: string
})
updateProjectDetailsAction
updateProjectDetailsAction({
projectId: string,
name: string,
slug: string,
description: string | null,
active: boolean,
accountSlug: string
})
deleteProjectAction
deleteProjectAction({
projectId: string,
accountSlug: string
})
RPC Functions
get_project_by_id
Returns project data if user has access
get_project_by_id(project_id uuid)
is_project_slug_unique
Validates slug uniqueness within account
is_project_slug_unique( p_account_id uuid, p_slug varchar, p_project_id uuid DEFAULT NULL )
Best Practices
1. Slug Management
- Use auto-generation for consistency
- Preserve manual customizations
- Validate uniqueness server-side
- Use debouncing to reduce validation calls
2. Permissions
- Always check permissions in server actions
- Use RLS for database-level security
- Respect role hierarchy in UI
- Hide actions user cannot perform
3. Error Handling
- Show user-friendly error messages
- Log errors server-side
- Use toast notifications for feedback
- Handle redirect errors specially
4. Performance
- Use React Server Components for data fetching
- Implement proper caching strategies
- Lazy load heavy components
- Debounce expensive operations