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
- Check whether the app already exists (by id or nameSlug) before calling `create()`; if it does, call `update()` instead.
- Remove the existing app record first if a clean re-install is intended.
- 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
- Check for an existing record by id/nameSlug before calling `create()`.
- Use `update()` for re-installs instead of `create()`.
- Ensure published apps have unique nameSlugs.
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
- Invalid path configured for file system App storage
- Invalid source path
- error-essential-app-disabled
- Invalid Api parameter provided, it must be a valid IApi obje
- Invalid command parameter provided, must be a string.
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/9e9bbdc4d0066c95.
Report an issue: GitHub.