Cal.com apps can implement various service types depending on their functionality. Each service type provides specific interfaces and lifecycle hooks.
Service Types
Calendar Apps
Calendar apps sync booking data with external calendar services.
Category: calendar
Service File: lib/CalendarService.ts
Interface
import type { Calendar, CalendarEvent, IntegrationCalendar } from "@calcom/types/Calendar";
class MyCalendarService implements Calendar {
constructor(credential: Credential) {
// Initialize with stored credentials
}
async createEvent(event: CalendarEvent): Promise<NewCalendarEventType> {
// Create event in external calendar
return {
uid: "external-event-id",
id: "external-event-id",
type: "my_calendar",
password: "",
url: "https://example.com/event/123",
};
}
async updateEvent(
uid: string,
event: CalendarEvent,
externalCalendarId?: string
): Promise<NewCalendarEventType> {
// Update existing event
}
async deleteEvent(
uid: string,
event: CalendarEvent,
externalCalendarId?: string
): Promise<void> {
// Delete event from external calendar
}
async getAvailability(
dateFrom: string,
dateTo: string,
selectedCalendars: IntegrationCalendar[]
): Promise<EventBusyDate[]> {
// Fetch busy times from external calendar
return [
{
start: "2024-01-01T10:00:00Z",
end: "2024-01-01T11:00:00Z",
},
];
}
async listCalendars(): Promise<IntegrationCalendar[]> {
// List available calendars for the user
return [
{
externalId: "calendar-id",
integration: "my_calendar",
name: "My Calendar",
primary: true,
},
];
}
}
export default MyCalendarService;
Registration
Export as default from lib/CalendarService.ts. The build process automatically registers it:
// Generated in calendar.services.generated.ts
export const CalendarServiceMap = {
"my-calendar": import("./my-calendar/lib/CalendarService"),
};
OAuth Support
For OAuth-based calendars, set isOAuth: true in config.json:
{
"isOAuth": true,
"variant": "calendar"
}
CRM Apps
CRM apps sync contact and booking data with customer relationship management systems.
Category: crm
Service File: lib/CrmService.ts
Interface
import type { CRM, Contact, CRMEvent } from "@calcom/types/CRM";
class MyCrmService implements CRM {
constructor(credential: Credential) {
// Initialize with credentials
}
async createEvent(event: CalendarEvent, contacts: Contact[]): Promise<void> {
// Create or update contacts and event in CRM
}
async updateEvent(uid: string, event: CalendarEvent): Promise<void> {
// Update CRM records
}
async deleteEvent(uid: string): Promise<void> {
// Delete from CRM
}
async getContacts(emails: string[]): Promise<Contact[]> {
// Fetch existing contacts
return [
{
email: "[email protected]",
id: "crm-contact-id",
name: "John Doe",
},
];
}
}
export default MyCrmService;
Example Apps
- Attio -
packages/app-store/attio/
- Basecamp3 -
packages/app-store/basecamp3/
Payment Apps
Payment apps process payments for bookings.
Category: payment
Service File: lib/PaymentService.ts
Interface
import type { Payment, PaymentData } from "@calcom/types/Payment";
export class PaymentService implements Payment {
constructor(private credential: Credential) {}
async create(payment: PaymentData) {
// Create payment intent
return {
id: "payment-intent-id",
success: false,
url: "https://payment-provider.com/checkout/xyz",
};
}
async update(paymentId: string, data: Partial<PaymentData>) {
// Update payment
}
async refund(paymentId: string): Promise<PaymentData> {
// Process refund
}
async afterPayment(
event: CalendarEvent,
booking: Booking,
paymentData: PaymentData
): Promise<void> {
// Post-payment processing (send receipts, etc.)
}
async deletePayment(paymentId: string): Promise<boolean> {
// Cancel payment
}
}
Payment Options
Define payment timing in zod.ts:
import { z } from "zod";
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod";
export const PaymentOptions = [
{
label: "on_booking_option",
value: "ON_BOOKING",
},
{
label: "after_booking_option",
value: "AFTER_BOOKING",
},
];
export const appDataSchema = eventTypeAppCardZod.merge(
z.object({
price: z.number(),
currency: z.string(),
paymentOption: z.string().optional(),
enabled: z.boolean().optional(),
credentialId: z.number().optional(),
})
);
Example Apps
- Alby (Bitcoin) -
packages/app-store/alby/
- BTCPay Server -
packages/app-store/btcpayserver/
Video Apps
Video apps provide video conferencing for bookings.
Category: conferencing
Service File: lib/VideoApiAdapter.ts
Interface
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
class MyVideoService implements VideoApiAdapter {
constructor(private credential: Credential) {}
async createMeeting(event: CalendarEvent): Promise<VideoCallData> {
// Create video meeting room
return {
type: "my_video",
id: "meeting-id",
password: "meeting-password",
url: "https://video-provider.com/join/xyz",
};
}
async updateMeeting(
bookingRef: PartialReference,
event: CalendarEvent
): Promise<VideoCallData> {
// Update meeting details
}
async deleteMeeting(uid: string): Promise<void> {
// Delete meeting room
}
async getAvailability(dateFrom: string, dateTo: string): Promise<any[]> {
// Optional: Check video service availability
return [];
}
}
export default MyVideoService;
Static Video Locations
For video services without dynamic room creation, use the event-type-location-video-static template:
{
"variant": "conferencing",
"appData": {
"location": {
"type": "my_video",
"label": "My Video",
"linkType": "static"
}
}
}
Analytics Apps
Analytics apps track booking metrics and user behavior.
Category: analytics
Service File: lib/AnalyticsService.ts
Interface
class MyAnalyticsService {
constructor(private credential: Credential) {}
async track(event: string, data: Record<string, any>): Promise<void> {
// Send tracking event
}
async identify(userId: string, traits: Record<string, any>): Promise<void> {
// Identify user
}
}
export default MyAnalyticsService;
Tag-Based Analytics
For tag/script injection (Google Analytics, GTM, etc.), use the booking-pages-tag template:
components/TagManagerScript.tsx
export default function TagManagerScript() {
return (
<script
dangerouslySetInnerHTML={{
__html: `
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXX');
`,
}}
/>
);
}
Automation Apps
Automation apps trigger workflows based on booking events.
Category: automation
Webhook-Based
Most automation apps use webhooks:
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { triggerEvent, payload } = req.body;
switch (triggerEvent) {
case "BOOKING_CREATED":
// Handle booking creation
break;
case "BOOKING_RESCHEDULED":
// Handle reschedule
break;
case "BOOKING_CANCELLED":
// Handle cancellation
break;
case "MEETING_ENDED":
// Handle meeting end
break;
}
return res.status(200).json({ received: true });
}
App Features
Event Type Extensions
Apps can add UI to event type configuration using extendsFeature: "EventType":
components/EventTypeAppCardInterface.tsx
import { useFormContext } from "react-hook-form";
export default function EventTypeAppCard() {
const { register } = useFormContext();
return (
<div>
<label>
<input type="checkbox" {...register("enabled")} />
Enable for this event type
</label>
</div>
);
}
App Settings
Provide a settings interface for installed apps:
components/AppSettingsInterface.tsx
import { useState } from "react";
export default function AppSettings({ credential }: { credential: Credential }) {
const [settings, setSettings] = useState(credential.key);
return (
<div>
<h3>App Settings</h3>
{/* Settings form */}
</div>
);
}
Custom installation flow:
components/InstallAppButton.tsx
export default function InstallAppButton() {
const handleInstall = async () => {
// Custom installation logic
};
return <button onClick={handleInstall}>Install</button>;
}
Configuration Options
config.json
Complete configuration reference:
{
"name": "App Name",
"slug": "app-slug",
"type": "app-slug_category",
"logo": "icon.svg",
"url": "https://example.com",
"variant": "category",
"categories": ["category"],
"publisher": "Publisher Name",
"email": "[email protected]",
"description": "Short description",
"extendsFeature": "EventType",
"isTemplate": false,
"isOAuth": false,
"dependencies": ["other-app-slug"],
"appData": {
"location": {
"type": "app-slug",
"label": "App Name",
"linkType": "dynamic"
},
"tag": {
"script": "https://example.com/script.js"
}
},
"externalLink": {
"url": "https://external-service.com",
"newTab": true
}
}
Display name shown in the app store
URL-safe identifier (auto-generated from name)
Format: {slug}_{category}
Must match category (or “conferencing” for video category)
“EventType” - Adds settings to event types
Whether the app uses OAuth authentication
Other apps required for this app to function
Best Practices
Security
- Never expose
credential.key in API responses
- Use
appKeysSchema for sensitive credentials
- Validate all inputs with Zod schemas
- Use environment-specific API endpoints
- Implement caching for external API calls
- Use lazy imports for UI components
- Batch operations when possible
- Handle rate limiting gracefully
Error Handling
import { ErrorWithCode } from "@calcom/lib/errorWithCode";
try {
await externalService.createEvent(event);
} catch (error) {
throw new ErrorWithCode(
`Failed to create event in ${serviceName}: ${error.message}`,
"SERVICE_ERROR"
);
}
Testing
Test your service implementations:
import { describe, it, expect } from "vitest";
import MyCalendarService from "./CalendarService";
describe("MyCalendarService", () => {
it("creates events", async () => {
const service = new MyCalendarService(mockCredential);
const result = await service.createEvent(mockEvent);
expect(result.uid).toBeDefined();
});
});
Examples by Type
Calendar
- Apple Calendar -
packages/app-store/applecalendar/
- CalDAV -
packages/app-store/caldavcalendar/
CRM
- Attio -
packages/app-store/attio/
- Basecamp3 -
packages/app-store/basecamp3/
Payment
- Alby -
packages/app-store/alby/
- BTCPay Server -
packages/app-store/btcpayserver/
Video
- Huddle01 -
packages/app-store/huddle01video/
Automation
- Make -
packages/app-store/make/
Next Steps
Build an App
Complete guide to building your first app
CLI Reference
App Store CLI command reference
Type Definitions
Explore TypeScript type definitions