Skip to main content
The CharleOS database schema is defined in lib/db/schema.ts with 47 tables organized into functional groups. This page provides a comprehensive reference.

Schema Overview

Entity Relationships

User (team)
├── assigns → Tasks
├── schedules → Subtasks
└── logs → Time Entries

Client
├── subscribes to → Plan
├── has → ClientUsers (portal access)
├── requests → Quotes
│   └── convert to → Tasks
├── owns → Projects
├── receives → Blocks (scheduled work)
└── tracked in → ClientPeriods (billing)

Task
├── contains → Subtasks (phases)
├── tracks → TimeEntries
├── part of → Project (optional)
└── converts from → Quote

Quote
├── contains → QuoteRequirements
└── approved by → Client

Core Tables

Authentication & Users

user - Team Members

Internal team members who use CharleOS.
ColumnTypeDescription
idtextPrimary key (CUID)
nametextFull name
emailtextEmail (unique, @charle.co.uk)
statusenumpending, sent, active, deactivated
workTypeenumdevelopment, design, pm, csm, qa, sdr
accessLevelenumadmin, manager, staff
hasBillableCapacitybooleanTrack for utilization metrics?
hasExecutiveAccessbooleanAccess to commercials dashboard?
engagementTypeenumretainer, project (for ICs)
departmentIdtextDepartment assignment
reportsToIdtextManager (references user.id)
slackUserIdtextCached Slack user ID
birthdaytextFormat: MM-DD
joinDatetimestampEmployment start date
Access Levels:
  • Admin: Full system access (Luke)
  • Manager: Management access (Simon, Andre, Ben, Nic)
  • Staff: Standard access (developers, designers, PMs, CSMs)
Work Types:
  • development, design, qa: Individual Contributors (ICs)
  • pm: Project Managers
  • csm: Client Success Managers
  • sdr: Sales Development Representatives

session & account

Better Auth tables for session management and OAuth accounts.
  • session: Active login sessions (expires after 7 days)
  • account: OAuth provider connections (Google)

user_preference

User UI preferences (key-value store).
ColumnTypeDescription
userIdtextReferences user.id
keytextPreference name (e.g., “sidebarCollapsed”)
valuetextJSON string value

Client Management

client - Client Companies

Companies that hire Charle for work.
ColumnTypeDescription
idtextPrimary key
nametextCompany name
slugtextURL-friendly identifier (unique)
csmIdtextAssigned CSM (references user.id)
planIdtextPricing plan (references plan.id)
monthlyHoursOverrideintegerOverride plan hours
monthlyCostOverrideintegerOverride plan cost (pence)
startDatetimestampClient start date
statusenumactive, archived
currentRagStatusenumgreen, amber, red
slackChannelIdtextSlack integration channel

client_period - Billing Periods

Tracks budget usage per billing period (monthly).
ColumnTypeDescription
clientIdtextReferences client.id
periodStarttimestampStart of period
periodEndtimestampEnd of period
allocatedMinutesintegerBudget for period
usedMinutesintegerActual time logged
soldDayRateintegerCalculated sold rate (pence)
actualDayRateintegerCalculated actual rate (pence)

client_user - Client Portal Users

Client employees who access the client portal.
ColumnTypeDescription
clientIdtextParent client company
emailtextEmail (unique)
nametextFull name
roleenumadmin, member
statusenumpending, sent, active, inactive
inviteTokentextInvitation token
Separate Auth System:
  • Client portal uses Better Auth with separate tables
  • client_session for sessions
  • client_account for credentials
  • Completely isolated from team auth

plan - Pricing Tiers

Retainer plan configurations.
ColumnTypeDescription
nametextPlan name (e.g., “Retainer 100”)
monthlyHoursintegerIncluded hours per month
monthlyCostintegerCost in pence

Quote System

quote - Work Requests

Requests for work that go through approval workflow.
ColumnTypeDescription
displayIdintegerSequential ID (QUOTE-123)
clientIdtextClient requesting work
titletextQuote title
descriptiontextBrief description (rich text)
statusenumawaiting_quote, in_progress, awaiting_csm_review, awaiting_client_approval, approved, cancelled
originenuminternal, client_request
estimatedTimeintegerTotal estimated minutes
createdByIdtextWho created it
Quote Workflow:
  1. awaiting_quote - Created, needs scoping
  2. in_progress - Being scoped by team
  3. awaiting_csm_review - Ready for CSM review
  4. awaiting_client_approval - Sent to client
  5. approved - Client approved (converts to task)
  6. cancelled - Client cancelled

quote_requirement - Requirement Blocks

Individual work items within a quote.
ColumnTypeDescription
quoteIdtextParent quote
typeenumdevelopment, design, seo, retain
titletextRequirement title
briefContenttextBrief description (rich text JSON)
scopeContenttextDetailed scope (rich text JSON)
tshirtSizeenumxs, s, m, l, xl, xxl, not_required
estimatedMinutesintegerTotal time (from t-shirt size)
workSplitCoreintegerCore work minutes (80%)
workSplitFeedbackintegerQA/feedback minutes (20%)
isCompletebooleanScoping complete?
scopedByIdtextWho scoped it
T-shirt Sizing:
  • XS: 15-30 min
  • S: 30-60 min
  • M: 1-4 hours
  • L: 4-8 hours
  • XL: 8-16 hours
  • XXL: 16-32 hours
80/20 Split:
  • Core work (80%) rounded down
  • QA/feedback budget (20%) rounded up to nearest 15 min

Task System

task - Approved Work

Work items created from approved quotes or directly.
ColumnTypeDescription
displayIdintegerSequential ID (TASK-123)
quoteIdtextSource quote (if converted)
clientIdtextClient (optional for internal)
projectIdtextParent project (optional)
titletextTask title
descriptiontextTask description (rich text)
pmIdtextAssigned project manager
priorityintegerPriority (1-5)
statusenumTask status
estimatedMinutesintegerTotal estimate

subtask - Task Phases

Individual work phases within a task.
ColumnTypeDescription
taskIdtextParent task
typeenumSubtask type (see below)
phaseenumdesign, development, qa, deployment
statusenumawaiting_scheduling, scheduled, in_progress, awaiting_review, complete
assigneeIdtextWho’s doing the work
estimatedMinutesintegerTime estimate
actualMinutesintegerActual time logged
iterationintegerFeedback cycle number
reviewOutcomeenumpending, approved, rejected
Subtask Types: Design Phase:
  • design - Initial design work
  • client_design_feedback - Feedback round
Development Phase:
  • development - Build work
  • internal_qa_fixes - Internal QA fixes
  • external_qa_fixes - Client QA fixes
QA Phase:
  • internal_qa_review - Internal review
  • external_qa_review - Client review
Deployment:
  • deployment - Deploy to production

Time Tracking

time_entry - Logged Time

Time logged against clients, tasks, and subtasks.
ColumnTypeDescription
userIdtextWho logged the time
clientIdtextClient being billed
taskIdtextRelated task (optional)
subtaskIdtextRelated subtask (optional)
durationintegerMinutes worked
datedateDate of work
descriptiontextWork description
categorytextType of work

Projects

project - Multi-Phase Projects

Long-running projects with budgeted phases.
ColumnTypeDescription
clientIdtextClient
nametextProject name
descriptiontextDescription
statusenumplanning, in_progress, on_hold, completed
totalBudgetMinutesintegerTotal allocated time
usedMinutesintegerTime used across tasks

project_phase - Budgeted Phases

Budget allocations within a project.
ColumnTypeDescription
projectIdtextParent project
nametextPhase name
budgetMinutesintegerAllocated time
usedMinutesintegerTime used
statusenumPhase status

Scheduling

client_block - Scheduled Work

Blocks of scheduled time on the calendar.
ColumnTypeDescription
clientIdtextClient
userIdtextAssigned user
subtaskIdtextRelated subtask (optional)
startDatedateStart date
endDatedateEnd date
hoursrealHours per day
typeenumsubtask, retainer, placeholder
statusenumscheduled, in_progress, complete, cancelled

Metrics & Reporting

metric_snapshot - Daily Metrics

Daily snapshots of utilization, day rates, and efficiency.
ColumnTypeDescription
datedateSnapshot date
entityTypeenumuser, client, department, agency
entityIdtextEntity identifier
utilizationPercentintegerUtilization %
soldDayRateintegerSold day rate (pence)
actualDayRateintegerActual day rate (pence)
bankedMinutesintegerEfficiency gain
overageMinutesintegerNon-billable overage

Help Desk

help_desk_ticket - Support Tickets

Client support requests (converted to subtasks).
ColumnTypeDescription
displayIdintegerTicket number
clientIdtextClient
titletextTicket title
descriptiontextDescription
typeenumbug, feature_request, question, other
priorityenumlow, medium, high, critical
statusenumTicket status
subtaskIdtextCreated subtask

Annual Leave

annual_leave - Leave Requests

Team member leave requests and approvals.
ColumnTypeDescription
userIdtextTeam member
startDatetimestampLeave start
endDatetimestampLeave end
leaveTypeenumannual, sick, unpaid, compassionate, birthday, other
dayTypeenumfull_day, half_day_am, half_day_pm
statusenumpending, approved, rejected, cancelled
reviewerIdtextWho approved/rejected

RAG Reports

rag_report - Client Status Reports

Weekly Red/Amber/Green client health reports.
ColumnTypeDescription
clientIdtextClient
weekStartingdateWeek start date
statusenumdraft, completed
ragStatusenumgreen, amber, red
keyPointstextSummary points
riskstextRisk assessment

Enums Reference

User & Access

type UserStatus = "pending" | "sent" | "active" | "deactivated";
type WorkType = "development" | "design" | "pm" | "csm" | "qa" | "sdr";
type AccessLevel = "admin" | "manager" | "staff";
type EngagementType = "retainer" | "project";

Task & Subtask

type TaskStatus = "awaiting_scheduling" | "scheduled" | "in_progress" | 
                  "awaiting_review" | "complete" | "cancelled";
type SubtaskType = "design" | "client_design_feedback" | "development" | 
                   "internal_qa_fixes" | "external_qa_fixes" | 
                   "internal_qa_review" | "external_qa_review" | "deployment";
type TaskPhase = "design" | "development" | "qa" | "deployment";

Quote

type QuoteStatus = "awaiting_quote" | "in_progress" | "awaiting_csm_review" | 
                   "awaiting_client_approval" | "approved" | "cancelled";
type TshirtSize = "xs" | "s" | "m" | "l" | "xl" | "xxl" | "not_required";

Type Inference

Drizzle automatically generates TypeScript types from the schema:
import { user, task } from "@/lib/db/schema";

// Select type (what you get from DB)
type User = typeof user.$inferSelect;

// Insert type (what you send to DB)
type NewUser = typeof user.$inferInsert;

// Usage
function processUser(user: User) {
  console.log(user.name); // ✅ TypeScript knows this exists
}

Relationships

Foreign Keys

All relationships use foreign keys with appropriate cascade rules:
  • onDelete: "cascade" - Delete children when parent is deleted
  • onDelete: "set null" - Set to null when parent is deleted
  • Default: Restrict deletion if children exist

Common Joins

// Task with client
const tasksWithClients = await db
  .select()
  .from(task)
  .leftJoin(client, eq(task.clientId, client.id));

// Subtask with task and assignee
const subtasksWithDetails = await db
  .select()
  .from(subtask)
  .leftJoin(task, eq(subtask.taskId, task.id))
  .leftJoin(user, eq(subtask.assigneeId, user.id));

Modifying the Schema

1

Edit Schema File

Modify lib/db/schema.ts to add/change tables or columns
2

Test Locally

npm run db:push
Applies changes to local dev database instantly
3

Verify in Studio

npm run db:studio
Browse updated schema visually
4

Generate Migration

npm run db:generate
Creates SQL migration file for production
5

Commit & Deploy

Commit schema + migration files. Migrations run automatically on deployment.