This guide walks you through creating a custom Cal.com app using the app-store CLI.
Prerequisites
Cal.com development environment set up
Node.js 10+ installed
Yarn package manager
Basic knowledge of TypeScript and React
Step 1: Create Your App
Use the app-store CLI to scaffold a new app:
Interactive Mode
cd packages/app-store-cli
yarn cli create
The CLI will prompt you for:
App name - Display name for your app
Description - Short description (keep under 10 words)
Category - Choose from: analytics, automation, calendar, conferencing, crm, messaging, payment, other
Publisher - Your name or company
Email - Contact email
Template - Starting template for your app
Non-Interactive Mode
For automation or CI/CD, pass all options as flags:
yarn cli create \
--name "My Custom App" \
--description "Integrates with external service" \
--category other \
--publisher "Your Name" \
--email "[email protected] " \
--template basic
The app slug is automatically generated from the name by converting to lowercase and replacing spaces with hyphens.
Step 2: Choose a Template
The CLI offers several templates based on your app’s functionality:
basic
A minimal app that can be installed with no additional features. Good starting point for simple integrations.
event-type-app-card
Adds a card to the event type configuration. Use for apps that extend event types (e.g., Giphy, QR Code).
Use case: Payment apps, custom booking fields, event-specific features
booking-pages-tag
Injects tags into booking pages (e.g., analytics, tracking scripts).
Use case: Google Analytics, Facebook Pixel, custom tracking
event-type-location-video-static
Adds a static video conferencing location to event types.
Use case: Custom video providers that don’t require dynamic room creation
general-app-settings
Provides a settings interface in the app configuration.
Use case: Apps requiring user configuration
link-as-an-app
Creates an app that redirects to an external URL.
Use case: External tools, documentation links
yarn cli create \
--name "External Tool" \
--description "Link to external service" \
--category other \
--template link-as-an-app \
--external-link-url "https://external-service.com"
Step 3: App Structure
Your new app is created in packages/app-store/your-app-slug/ with this structure:
my-custom-app/
├── config.json # App configuration
├── package.json # Dependencies
├── index.ts # Exports
├── zod.ts # Validation schemas
├── DESCRIPTION.md # App store description
├── api/
│ ├── index.ts # API exports
│ └── add.ts # Installation handler
├── components/ # React components (template-dependent)
└── static/
└── icon.svg # App icon
Edit config.json to customize your app’s metadata:
{
"name" : "My Custom App" ,
"slug" : "my-custom-app" ,
"type" : "my-custom-app_other" ,
"logo" : "icon.svg" ,
"url" : "https://example.com" ,
"variant" : "other" ,
"categories" : [ "other" ],
"publisher" : "Your Name" ,
"email" : "[email protected] " ,
"description" : "Short app description" ,
"extendsFeature" : "EventType" ,
"isOAuth" : false
}
Don’t modify the slug field directly. Use yarn cli edit --slug your-app if you need to change it.
Step 5: Add Dependencies
Update package.json to include any required dependencies:
{
"name" : "@calcom/my-custom-app" ,
"version" : "0.0.0" ,
"private" : true ,
"main" : "./index.ts" ,
"dependencies" : {
"@calcom/lib" : "workspace:*" ,
"axios" : "^1.6.0"
},
"devDependencies" : {
"@calcom/types" : "workspace:*"
}
}
Install dependencies:
Step 6: Define Schemas
Edit zod.ts to define validation schemas for your app:
import { z } from "zod" ;
import { eventTypeAppCardZod } from "@calcom/app-store/eventTypeAppCardZod" ;
// Schema for app settings on event types
export const appDataSchema = eventTypeAppCardZod . merge (
z . object ({
enabled: z . boolean (). optional (),
apiKey: z . string (). optional (),
customSetting: z . string (). default ( "default-value" ),
})
);
// Schema for app credentials (stored securely)
export const appKeysSchema = z . object ({
api_key: z . string (). min ( 1 ),
api_secret: z . string (). min ( 1 ),
webhook_url: z . string (). url (). optional (),
});
appKeysSchema credentials are stored encrypted and never exposed in API responses.
Step 7: Implement Installation Handler
Edit api/add.ts to handle app installation:
import type { NextApiRequest , NextApiResponse } from "next" ;
import { appKeysSchema } from "../zod" ;
export default async function handler (
req : NextApiRequest ,
res : NextApiResponse
) {
if ( req . method !== "POST" ) {
return res . status ( 405 ). json ({ message: "Method not allowed" });
}
try {
// Validate incoming credentials
const credentials = appKeysSchema . parse ( req . body );
// Verify credentials with external service
const isValid = await verifyCredentials ( credentials );
if ( ! isValid ) {
return res . status ( 401 ). json ({ message: "Invalid credentials" });
}
// Store credentials (handled by Cal.com)
return res . status ( 200 ). json ({
message: "App installed successfully"
});
} catch ( error ) {
return res . status ( 400 ). json ({
message: "Invalid request" ,
error: error . message
});
}
}
async function verifyCredentials ( credentials : any ) : Promise < boolean > {
// Implement your credential verification logic
return true ;
}
Step 8: Add Business Logic
Create service files in the lib/ directory based on your app type:
Calendar App
packages/app-store/my-calendar/lib/CalendarService.ts
import type { Calendar , CalendarEvent } from "@calcom/types/Calendar" ;
export default class MyCalendarService implements Calendar {
constructor ( private credential : any ) {}
async createEvent ( event : CalendarEvent ) {
// Implement event creation
}
async updateEvent ( uid : string , event : CalendarEvent ) {
// Implement event update
}
async deleteEvent ( uid : string ) {
// Implement event deletion
}
async getAvailability ( dateFrom : string , dateTo : string ) {
// Implement availability check
}
}
Payment App
packages/app-store/my-payment/lib/PaymentService.ts
import type { Payment , PaymentData } from "@calcom/types/Payment" ;
export class PaymentService implements Payment {
async create ( paymentData : PaymentData ) {
// Create payment intent
}
async update ( paymentId : string , data : Partial < PaymentData >) {
// Update payment
}
async refund ( paymentId : string ) {
// Process refund
}
async afterPayment ( event : CalendarEvent , booking : Booking ) {
// Post-payment processing
}
}
Step 9: Add UI Components
Create React components for your app’s UI:
packages/app-store/my-app/components/EventTypeAppCardInterface.tsx
import { useState } from "react" ;
import { useFormContext } from "react-hook-form" ;
export default function EventTypeAppCard () {
const { register } = useFormContext ();
return (
< div className = "space-y-4" >
< h3 > My App Settings </ h3 >
< label >
< input
type = "checkbox"
{ ... register ( "enabled" ) }
/>
Enable My App
</ label >
< input
type = "text"
placeholder = "API Key"
{ ... register ( "apiKey" ) }
/>
</ div >
);
}
Step 10: Generate App Files
Generate the app registry files:
cd packages/app-store-cli
yarn build
This creates *.generated.ts files that register your app with the Cal.com platform.
Step 11: Test Your App
Start the development server:
Navigate to http://localhost:3000/settings/admin/apps
Find your app in the list
Enable it as an admin
Install it from http://localhost:3000/apps/your-app-slug
Test the installation flow
Step 12: Add App Store Assets
Customize your app’s App Store presence:
DESCRIPTION.md
Add images and detailed description:
---
items :
- iframe : { src : https://www.youtube.com/embed/VIDEO_ID }
- screenshot-1.jpg
- screenshot-2.png
- screenshot-3.jpg
---
## Overview
Detailed description of your app and what it does.
## Features
- Feature 1
- Feature 2
- Feature 3
## Setup Instructions
Step-by-step guide to setting up your app.
Include at least 4 images. Use only filenames (e.g., 1.jpeg), not paths.
README.md
Add installation instructions:
# My Custom App
## Installation
1. Install the app from the Cal.com app store
2. Get your API credentials from https://example.com/api-keys
3. Enter your credentials in the app settings
4. Configure your event types
## Configuration
For OAuth-based apps, use `<baseUrl>/api/integrations` as the callback URL.
## Support
For questions or issues, contact [email protected]
Step 13: Handle Environment Variables
Never add environment variables directly to .env files. Use the zod.ts approach instead.
Define admin-configurable settings in zod.ts:
import { z } from "zod" ;
export const appKeysSchema = z . object ({
api_endpoint: z . string (). url (). default ( "https://api.example.com" ),
api_key: z . string (),
});
These settings will be available in /settings/admin for modification.
Watch Mode
During development, use watch mode to automatically regenerate files:
cd packages/app-store-cli
yarn watch
Troubleshooting
App Not Appearing
Ensure yarn build completed successfully
Check that config.json is valid JSON
Restart the dev server
Enable the app in /settings/admin/apps
Type Errors
Run type checks:
yarn type-check:ci --force
Generated Files Not Updating
cd packages/app-store-cli
yarn build
Next Steps
App Types Learn about different app service types
CLI Reference Complete CLI command reference
Contributing Guidelines for contributing apps to Cal.com
Examples Browse existing apps for examples