RocketChat/Rocket.Chat · error

App already exists.

Error message

App already exists.

What it means

Thrown by AppRealStorage.create when inserting a new app record would collide with an existing one on either id or info.nameSlug (a $or lookup runs before insert). The storage layer enforces uniqueness of the app ID and the URL-friendly app name so two installs of the same app (or two apps with the same slug) cannot coexist. It is a conflict error: the app is already installed or another app occupies the slug.

Solutions

  1. Check installed apps (Administration -> Apps) — if present, use update/re-enable instead of install.
  2. Fully uninstall the existing app (which removes its storage doc) and retry the install.
  3. Slug clash with a different app: uninstall the other app or rebuild your fork with a distinct app name so nameSlug differs.
  4. Orphaned doc: remove the stale document from the apps collection (support-guided) then reinstall.

Example fix

// before
await storage.create(item); // "App already exists." when id/nameSlug collides

// after: check before create
const existing = await storage.retrieveOne(item.id);
if (existing) {
  await storage.update(item.id, item); // in-place update path
} else {
  await storage.create(item);
}
Defensive patterns

Strategy: validation

Validate before calling

// Check-before-create (mirrors the storage guard)
const existing = await storage.retrieveOne(item.id);
const slugTaken = await storage.internalSearchByIds?; // or query by info.nameSlug
if (existing || slugTaken) throw new ConflictError('app-installed');

Try / catch

try {
  await storage.create(item);
} catch (e) {
  if (e instanceof Error && e.message === 'App already exists.') {
    await storage.update(item.id, item); // converge via update instead of failing the install
  }
}

Prevention

When it happens

Trigger: Installing an app whose id already exists in the apps collection, or whose nameSlug matches a different installed app (e.g. two builds of similarly named apps); concurrent installs of the same app racing past the findOne; reinstalling an app that was removed but whose DB document remained.

Common situations: Reinstalling an app that is actually still installed (stale UI state); installing a fork with the same name (slug clash); app removed outside the normal uninstall path leaving orphan docs; double-click/double-submit on install.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@2a7de45707 (2026-08-18). Data as JSON: /api/errors/9e9bbdc4d0066c95. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/ee/server/apps/storage/AppRealStorage.ts:23

import type { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata';
import type { ISetting } from '@rocket.chat/apps-engine/definition/settings';
import type { Apps } from '@rocket.chat/models';
import { removeEmpty } from '@rocket.chat/tools';
import type { UpdateFilter } from 'mongodb';

export class AppRealStorage extends AppMetadataStorage {
	constructor(private db: typeof Apps) {
		super('mongodb');
	}

	public async create(item: IAppStorageItem): Promise<IAppStorageItem> {
		item.createdAt = new Date();
		item.updatedAt = new Date();

		const doc = await this.db.findOne({ $or: [{ id: item.id }, { 'info.nameSlug': item.info.nameSlug }] });

		if (doc) {
			throw new Error('App already exists.');
		}

		const nonEmptyItem = removeEmpty(item);
		const id = (await this.db.insertOne(nonEmptyItem)).insertedId as unknown as string;
		nonEmptyItem._id = id;

		return nonEmptyItem;
	}

	public async retrieveOne(id: string): Promise<IAppStorageItem> {
		return this.db.findOne({ $or: [{ _id: id }, { id }] });
	}

	public async retrieveAll(): Promise<Map<string, IAppStorageItem>> {
		const docs = await this.db.find({}).toArray();
		const items = new Map();

		docs.forEach((i) => items.set(i.id, i));

View on GitHub (pinned to 2a7de45707)