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
- Check installed apps (Administration -> Apps) — if present, use update/re-enable instead of install.
- Fully uninstall the existing app (which removes its storage doc) and retry the install.
- Slug clash with a different app: uninstall the other app or rebuild your fork with a distinct app name so nameSlug differs.
- 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
- Idempotent install flows: retrieve-then-create/update instead of blind create.
- Disable the install button while an install request is in flight to prevent double submits.
- Ensure forks use unique app names so nameSlug never collides with marketplace apps.
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
- The user for app is not registered.
- This instance could not remove the
- A new user type has been added that the Apps don't know…
- A video conference must exist to update.
- app-addon-not-valid
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)