Core Principles
As a Cal.com contributor, you should prioritize:
- Type Safety - Use TypeScript strictly, avoid
any
- Security - Never expose sensitive data or credentials
- Small PRs - Keep changes focused and reviewable (<500 lines, <10 files)
- Code Quality - Write clean, maintainable code that explains “why”, not “what”
Do’s and Don’ts
Always Do
Use select instead of include in Prisma queries
Use import type { X } for TypeScript type imports
Use early returns to reduce nesting
Use ErrorWithCode for non-tRPC errors, TRPCError for tRPC routers
Use conventional commits: feat:, fix:, refactor:
Import directly from source files, not barrel files
Add translations to packages/i18n/locales/en/common.json
Use date-fns or native Date when timezone awareness isn’t needed
Put permission checks in page.tsx, never in layout.tsx
Use Biome for formatting and linting
Run yarn type-check:ci --force before pushing
Never Do
Never use as any - use proper type-safe solutions
Never expose credential.key field in API responses
Never commit secrets, API keys, or .env files
Never modify *.generated.ts files directly
Never put business logic in repositories
Never use barrel imports from index.ts files
Never skip running type checks before pushing
Never create large PRs (>500 lines or >10 files)
Never add comments that restate what code does
TypeScript Best Practices
Type Imports
// Good - Type imports
import type { User } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
// Bad - Regular imports for types
import { User } from "@prisma/client";
Avoid any
// Good - Proper typing
function processBooking(booking: Booking): BookingResponse {
return { id: booking.id, status: booking.status };
}
// Bad - Using any
function processBooking(booking: any): any {
return booking;
}
Use Early Returns
// Good - Early returns reduce nesting
function getBooking(id: string) {
const booking = await prisma.booking.findUnique({ where: { id } });
if (!booking) return null;
const user = await prisma.user.findUnique({ where: { id: booking.userId } });
if (!user) return null;
return { ...booking, user };
}
// Bad - Nested conditions
function getBooking(id: string) {
const booking = await prisma.booking.findUnique({ where: { id } });
if (booking) {
const user = await prisma.user.findUnique({ where: { id: booking.userId } });
if (user) {
return { ...booking, user };
}
}
return null;
}
Prisma Best Practices
Use select Over include
// Good - Explicit field selection
const booking = await prisma.booking.findFirst({
select: {
id: true,
title: true,
startTime: true,
user: {
select: {
id: true,
name: true,
email: true,
},
},
},
});
// Bad - Includes all fields (performance and security issue)
const booking = await prisma.booking.findFirst({
include: {
user: true,
},
});
Using select improves performance and prevents accidental exposure of sensitive data like credential.key.
Never Expose Credentials
// Good - Exclude sensitive fields
const credentials = await prisma.credential.findMany({
select: {
id: true,
type: true,
userId: true,
// Never select credential.key
},
});
// Bad - Exposes sensitive data
const credentials = await prisma.credential.findMany();
Import Best Practices
Direct Imports, Not Barrel Files
// Good - Direct imports
import { Button } from "@calcom/ui/components/button";
import { Dialog } from "@calcom/ui/components/dialog";
// Bad - Barrel imports (slower, breaks tree-shaking)
import { Button, Dialog } from "@calcom/ui";
API v2 Imports
When importing into apps/api/v2, re-export from platform libraries:
// Step 1: In packages/platform/libraries/index.ts
export { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository";
// Step 2: In apps/api/v2, import from platform-libraries
import { ProfileRepository } from "@calcom/platform-libraries";
// Bad - Direct import causes module not found error
import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository";
Error Handling
Use Appropriate Error Classes
// Good - TRPCError in tRPC routers
import { TRPCError } from "@trpc/server";
throw new TRPCError({
code: "NOT_FOUND",
message: `Booking ${bookingId} not found for user ${userId}`,
});
// Good - ErrorWithCode in services/utilities
import { ErrorWithCode } from "@calcom/lib/error";
throw new ErrorWithCode(
`Unable to create booking: User ${userId} has no available time slots for ${date}`,
"BOOKING_UNAVAILABLE"
);
// Bad - Generic error without context
throw new Error("Booking failed");
Descriptive Error Messages
Always provide context in error messages:
// Good - Context included
throw new Error(
`Unable to send email to ${email}: SMTP connection failed with code ${errorCode}`
);
// Bad - No context
throw new Error("Email failed");
File Naming Conventions
Services
// Pattern: <Entity>Service.ts
// File: MembershipService.ts
export class MembershipService { ... }
// File: HashedLinkService.ts
export class HashedLinkService { ... }
Repositories
// Pattern: Prisma<Entity>Repository.ts
// File: PrismaAppRepository.ts
export class PrismaAppRepository { ... }
// File: PrismaMembershipRepository.ts
export class PrismaMembershipRepository { ... }
- File names must match exported class names exactly (PascalCase)
- Avoid dot-suffixes like
.service.ts or .repository.ts (legacy patterns)
- Reserve suffixes for
.test.ts, .spec.ts, and .types.ts
Only add comments that explain why, not what.
// Good - Explains why
// We need to delay the webhook by 5 seconds to ensure the booking
// is fully committed before external systems receive the notification
await delay(5000);
// Bad - Restates what the code does
// Get the user
const user = await getUser();
Internationalization
Add all UI strings to translation files:
// packages/i18n/locales/en/common.json
{
"booking_confirmed": "Booking confirmed",
"booking_cancelled": "Booking cancelled"
}
// Usage in components
import { useLocale } from "@calcom/lib/hooks/useLocale";
const { t } = useLocale();
return <div>{t("booking_confirmed")}</div>;
Conventional Commits
Use conventional commit format for PR titles:
feat(bookings): add cancellation reason field
fix(calendar): resolve timezone conversion bug
refactor(auth): extract session validation logic
docs(api): update webhook documentation
test(bookings): add unit tests for reschedule flow
Format: <type>(<scope>): <description>
Types:
feat - New feature
fix - Bug fix
refactor - Code refactoring
docs - Documentation changes
test - Adding/updating tests
chore - Maintenance tasks
PR Size Guidelines
Keep PRs small and focused for faster reviews and easier debugging.
Size Limits
- Lines changed: <500 lines of code
- Files changed: <10 code files
- Single responsibility: Each PR should do one thing well
These limits exclude documentation, lock files, and auto-generated files.
Splitting Large Changes
By layer:
PR 1: Database schema changes
PR 2: Backend API endpoints
PR 3: Frontend UI components
PR 4: Integration and testing
By feature component:
PR 1: Add notification preferences schema
PR 2: Add notification service and API
PR 3: Add notification UI components
PR 4: Integrate notifications into booking flow
By refactor vs feature:
PR 1: Extract calendar logic into service
PR 2: Add new calendar provider support
Testing Requirements
Before Committing
# Type check
yarn type-check:ci --force
# Lint and format
yarn biome check --write .
# Run relevant tests
TZ=UTC yarn test
Before Pushing
Security Guidelines
Never Commit Secrets
# Add to .gitignore
.env
.env.local
credentials.json
import { z } from "zod";
// Good - Zod validation
const createBookingSchema = z.object({
eventTypeId: z.number(),
startTime: z.string().datetime(),
timeZone: z.string(),
});
const validated = createBookingSchema.parse(input);
Sanitize Output
// Good - Only return necessary fields
return {
id: user.id,
name: user.name,
email: user.email,
// Never include password, tokens, or credential keys
};
Boundaries
Ask First
- Adding new dependencies
- Schema changes to
packages/prisma/schema.prisma
- Changes affecting multiple packages
- Deleting files
- Running full build or E2E suites
Never Do Without Permission
- Commit secrets or API keys
- Force push to shared branches
- Modify generated files directly
- Expose sensitive data in APIs
PR Checklist
Before submitting a PR:
Code Review Guidelines
As a Contributor
- Respond to feedback promptly
- Ask questions if feedback is unclear
- Update your branch regularly
- Test suggested changes before pushing
As a Reviewer
- Be respectful and constructive
- Explain the “why” behind suggestions
- Approve when ready, don’t nitpick
- Use “Request changes” sparingly
Resources
Next Steps