Skip to main content
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 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

Step 4: Configure Your App

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:
yarn install

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:
yarn dev
  1. Navigate to http://localhost:3000/settings/admin/apps
  2. Find your app in the list
  3. Enable it as an admin
  4. Install it from http://localhost:3000/apps/your-app-slug
  5. 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

  1. Ensure yarn build completed successfully
  2. Check that config.json is valid JSON
  3. Restart the dev server
  4. 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

Build docs developers (and LLMs) love