RocketChat/Rocket.Chat · error · Error

App already exists.

Error message

App already exists.

What it means

Thrown by `AppRealStorage.create()` when a document already exists matching the new app's `id` OR its `info.nameSlug`. The storage enforces uniqueness on both the app ID and the human-readable name slug to prevent duplicate marketplace/installed apps. It is a plain `Error`, not a typed/Meteor error.

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 f9d3ec372b)

Solutions

  1. Check whether the app already exists (by id or nameSlug) before calling `create()`; if it does, call `update()` instead.
  2. Remove the existing app record first if a clean re-install is intended.
  3. Ensure app packages have unique nameSlugs when publishing to avoid collisions.

Example fix

// before
await Apps.create(item); // throws if duplicate

// after
const existing = await Apps.findOne({ $or: [{ id: item.id }, { 'info.nameSlug': item.info.nameSlug }] });
if (existing) {
	await Apps.update(item);
} else {
	await Apps.create(item);
}
Defensive patterns

Strategy: validation

Validate before calling

async function appExists(id: string, nameSlug: string): Promise<boolean> {
	const doc = await Apps.findOne({ $or: [{ id }, { 'info.nameSlug': nameSlug }] });
	return Boolean(doc);
}

Type guard

function isAppAlreadyExistsError(e: unknown): boolean {
	return e instanceof Error && e.message === 'App already exists.';
}

Try / catch

try {
	await Apps.create(item);
} catch (e) {
	if (e instanceof Error && e.message === 'App already exists.') {
		await Apps.update(item); // or skip
		return;
	}
	throw e;
}

Prevention

When it happens

Trigger: Calling `create(item)` where `this.db.findOne({ $or: [{ id: item.id }, { 'info.nameSlug': item.info.nameSlug }] })` returns a non-null document.

Common situations: Re-installing an app that is already in the DB (possibly in a removed/disabled state); two apps sharing the same nameSlug due to a packaging mistake; a retry of an install that partially succeeded.

Related errors


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