calcom/cal.com
Patterns and conventions
Cal.diy has a thick layer of conventions captured in AGENTS.md and the agents/rules/ directory. This page collects the ones a new contributor needs to internalize.
Imports
- No barrel imports. Import directly from source files:
import { Button } from "@calcom/ui/components/button", notimport { Button } from "@calcom/ui". This is enforced by code review and ESLint config. - Type-only imports.
import type { X } from "..."— keeps emitted JavaScript clean. apps/api/v2re-exports. Modules from@calcom/featuresand@calcom/trpccannot be imported directly intoapps/api/v2because that app'stsconfig.jsonlacks the path mappings. Re-export them frompackages/platform/libraries/index.tsand import from@calcom/platform-libraries. This is documented at length inAGENTS.md.
Repository / Service split
Every domain in packages/features/<domain>/ follows the same shape:
<domain>/
├── repositories/ # Prisma queries only — never logic
├── services/ or service/ # Business logic, calls repositories
├── di/ # Wires concrete implementations to interfaces
├── lib/ # Domain helpers
└── components/ # UI (if applicable)Repository rules:
- Use
select, neverinclude. The team treatsincludeas a security smell because it leaks every column. - Never expose
credential.key. The encrypted-credentials column is highlighted inAGENTS.mdDon'ts. - Repositories take a
PrismaClient(or transaction) in their constructor.
Service rules:
- Throw
ErrorWithCode(packages/lib/errorCodes.ts) — notError, notTRPCError. - One service typically owns one transactional unit (e.g.,
RegularBookingServiceorchestrates the whole booking creation).
tRPC and REST routers/controllers stay thin: they validate input, call a service, and return the result.
Errors
| Layer | Class to throw | File |
|---|---|---|
| tRPC handler | TRPCError |
@trpc/server |
| Service / repository / utility | ErrorWithCode |
packages/lib/errorCodes.ts |
HTTP (legacy pages/api) |
Throw, then catch + map | packages/lib/http-error.ts |
| API v2 NestJS | Standard NestJS exceptions or domain-specific filters | apps/api/v2/src/filters/ |
Concrete error codes live in packages/lib/errorCodes.ts (e.g., NoAvailableUsersFound, BookingConflict). Adding a new error means: add the code, throw ErrorWithCode, add a translated user-facing string in packages/i18n/locales/en/common.json.
Type safety
- Never
as any. Useas unknown as Fooonly when the cast is provably safe and document why. - Never re-declare types. Prefer types generated by Prisma (
@prisma/client) and Zod. The repo wireszod-prisma-typesfor this purpose. - Strict tsconfig.
tsconfig.jsonextendspackages/tsconfigpresets.strict: trueis the rule; relaxed configs require justification.
Database conventions
- Always use
selectin Prisma queries. - Wrap multi-write paths in transactions (
prisma.$transaction). Booking creation is a canonical example. - Long-running operations should be queued via the tasker (
packages/features/tasker) instead of run inline. - The
Credential.keycolumn is encrypted withCALENDSO_ENCRYPTION_KEY. Never select it into anything that could be returned to a client.
React conventions
- App Router pages live in
apps/web/app/. Permission checks belong inpage.tsx, neverlayout.tsx(AGENTS.md). - Pages that wrap the booker use the
(booking-page-wrapper)route group; authenticated dashboards use(use-page-wrapper). - Tailwind for styling. Component primitives come from
packages/ui/components/.... - Always use sentence case in headings.
- Translations: every UI string should resolve via
useLocale()againstpackages/i18n/locales/en/common.json.
Comments and code style
AGENTS.md is explicit:
Only add code comments that explain why, not what. Never add comments that simply restate what the code does.
The agents/rules/quality-code-comments.md file expands on this.
Generated files
The following must never be hand-edited:
packages/app-store/apps.metadata.generated.tsand friends (regenerate withyarn app-store:build)packages/prisma/generated/**(regenerate withyarn prisma generate)packages/prisma/zod/**packages/kysely/types.tsapps/api/docs/openapi.json(generated by Swagger)
Time and date
- The codebase historically used Day.js everywhere. Newer code prefers
date-fnsor nativeDatewhen timezone awareness isn't needed (AGENTS.md). - The pre-configured Day.js bundle lives in
packages/dayjs. Importing Day.js plugins ad hoc has bitten the team enough times that this single bundle is canonical.
Feature flags
packages/features/flags provides a database-backed feature-flag service. To gate a code path:
import { FeaturesRepository } from '@calcom/features/flags/features.repository';
const flags = new FeaturesRepository(prisma);
if (await flags.checkIfFeatureIsEnabledGlobally('my-feature')) {
/* ... */
}Don't ship hardcoded if (process.env.ENABLE_X) checks for product-facing toggles.
Search
- Prefer
ast-grepif installed. - Else
rg(ripgrep). - Avoid
grepandfind— they are slower and miss.gitignorerules.
PR hygiene
- Keep diffs <500 lines / <10 code files.
- Open as a draft.
- Conventional Commits in the title.
- No secrets, ever.
When you find yourself fighting these, you usually need to split the PR.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.