calcom/cal.diy · error · Error
App is already installed
Error message
App is already installed
What it means
Thrown by defaultIntegrationAddHandler when an app that does not support multiple installs already has a credential row for the same appId and user (or user+team). It is a plain Error (not HttpError), so it bubbles into the route's catch block and is normalized by getServerErrorFromUnknown before being sent to the client. This prevents duplicate OAuth/credential rows for single-install apps.
Source
Thrown at apps/web/pages/api/integrations/[...args].ts:38
slug: string;
supportsMultipleInstalls: boolean;
appType: string;
user?: Session["user"];
teamId?: number;
createCredential: AppDeclarativeHandler["createCredential"];
}) => {
if (!user?.id) {
throw new HttpError({ statusCode: 401, message: "You must be logged in to do this" });
}
if (!supportsMultipleInstalls) {
const alreadyInstalled = await prisma.credential.findFirst({
where: {
appId: slug,
...(teamId ? { AND: [{ userId: user.id }, { teamId }] } : { userId: user.id }),
},
});
if (alreadyInstalled) {
throw new Error("App is already installed");
}
}
await throwIfNotHaveAdminAccessToTeam({ teamId: teamId ?? null, userId: user.id });
await createCredential({ user: user, appType, slug, teamId });
};
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
// Check that user is authenticated
req.session = await getServerSession({ req });
const { args, teamId } = req.query;
if (!Array.isArray(args)) {
return res.status(404).json({ message: `API route not found` });
}
View on GitHub (pinned to 176037d0af)
Solutions
- Check the Credential table for an existing row with that appId + userId (or teamId) and uninstall it first if reinstall is intended.
- Refresh the integrations page so the UI shows the app as installed rather than offering install again.
- If the app should allow multiple installs, set supportsMultipleInstalls: true in the app's package config.
- Delete the orphaned credential row via the uninstall flow or directly in the DB, then retry.
Example fix
// before: installing again without checking
await installApp('zoom');
// after: guard with the provided helper
import { isAppInstalled } from '@calcom/app-store/_utils/installation';
if (!(await isAppInstalled({ appId: 'zoom', userId }))) {
await installApp('zoom');
} Defensive patterns
Strategy: validation
Validate before calling
import { isAppInstalled } from '@calcom/app-store/_utils/installation';
const alreadyInstalled = await isAppInstalled({ appId: slug, userId: user.id });
if (alreadyInstalled) {
return res.status(200).json({ message: 'Already installed', alreadyInstalled: true });
} Type guard
null
Try / catch
try {
await defaultIntegrationAddHandler({ ... });
} catch (e) {
if (e instanceof Error && e.message === 'App is already installed') {
return res.status(409).json({ message: e.message });
}
throw e;
} Prevention
- Call isAppInstalled before invoking the install path to short-circuit duplicates.
- Disable/debounce the install button in the UI after the first click.
- Treat 'App is already installed' as an idempotent success in the client.
When it happens
Trigger: Calling /api/integrations/<single-install-app>/add for a user who already has a Credential row with appId === slug (userId-scoped), or with both userId and teamId matching when a teamId query param is present. Common during repeated install button clicks or after a partially completed install left a credential behind.
Common situations: User clicked install twice; a previous OAuth redirect already created the credential but the UI did not refresh; stale credential row left after an uninstall that partially failed; testing installs repeatedly without cleaning the credential table.
Related errors
- Email already exists
- No valid credentials available for Google Calendar
- Google Calendar is not connected for this user
- Google Calendar credentials are invalid. Please reconnect.
- Calendar connection not found
AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12).
Data as JSON: /api/errors/1d9c783b76a18a0b.
Report an issue: GitHub.