RocketChat/Rocket.Chat · error

No app.json file found in the zip

Error message

No app.json file found in the zip

What it means

getAppManifest() in the marketplace app-install helper throws this when the zip buffer unzipped by fflate's unzipSync contains no entry keyed exactly 'app.json' — i.e. the apps-engine package layout is wrong. Every Rocket.Chat app package must have app.json at the root of the zip archive.

Solutions

  1. List the archive contents (unzip -l app.zip) and confirm app.json is at the root.
  2. Rebuild the zip from inside the app folder so entries are top-level: zip -r ../app.zip app.json src/ ... (or use the apps-engine packaging script).
  3. Verify the downloaded file is actually a zip (file app.zip) and not an HTML error payload.

Example fix

# before: my-app.zip
#   my-app/app.json   (nested → throws)

# after: my-app.zip
#   app.json          (at zip root)
cd my-app && zip -r ../my-app.zip app.json src lib
Defensive patterns

Strategy: validation

Validate before calling

import { unzipSync } from 'fflate';
const entries = Object.keys(unzipSync(buffer));
if (!entries.includes('app.json')) {
  dispatchToastMessage({ type: 'error', message: 'app.json must be at the zip root' });
}

Type guard

const hasRootManifest = (entries: string[]): boolean => entries.includes('app.json');

Prevention

When it happens

Trigger: Uploading an app zip where app.json lives in a nested folder ('my-app/app.json'), a zip created by zipping the parent directory instead of its contents, a download that produced an HTML error page instead of a zip, or an archive without a manifest at all.

Common situations: Developers running zip -r app.zip my-app/ instead of zipping from inside the folder; macOS double-compression (.zip.cpgz); CI packaging scripts changing the root layout; downloading apps from a URL that returned an error page.

Related errors


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

Appendix: source

Thrown at apps/meteor/client/views/marketplace/lib/getManifestFromZippedApp.ts:22

type Uint8ArrayObject = { [fileName: string]: Uint8Array };
type AppManifestSchema = { id: string; name: string; permissions: AppPermission[] };

async function fileToUint8Array(file: File): Promise<Uint8Array> {
	return new Promise((resolve, reject) => {
		const fileReader = new FileReader();
		fileReader.onload = (e): void => resolve(new Uint8Array((e.target as any).result as Uint8Array));
		fileReader.onerror = (e): void => reject(e);
		fileReader.readAsArrayBuffer(file);
	});
}

function unzipAppBuffer(zippedAppBuffer: Uint8Array): Uint8ArrayObject {
	return unzipSync(zippedAppBuffer);
}

function getAppManifest(unzippedAppBuffer: Uint8ArrayObject): AppManifestSchema {
	if (!unzippedAppBuffer['app.json']) {
		throw new Error('No app.json file found in the zip');
	}

	try {
		return JSON.parse(strFromU8(unzippedAppBuffer['app.json']));
	} catch (e) {
		throw new Error(`Failed to parse app.json: ${e instanceof Error ? e.message : String(e)}`);
	}
}

async function unzipZippedApp(zippedApp: File | Uint8Array): Promise<Uint8ArrayObject> {
	try {
		if (zippedApp instanceof File) {
			zippedApp = await fileToUint8Array(zippedApp);
		}

		return unzipAppBuffer(zippedApp);
	} catch (e) {
		console.error(e);

View on GitHub (pinned to b2c16d5842)