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
- List the archive contents (unzip -l app.zip) and confirm app.json is at the root.
- 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).
- 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
- Zip from inside the app folder so app.json is a root entry.
- Verify packages with unzip -l before publishing or uploading.
- Confirm downloads are real zips (not HTML error pages) before installing.
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
- app-addon-not-valid
- Failed to parse app.json
- invalid-license
- Marketplace_Invalid_Apps_Engine_Version
- No_results_found
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)