BabylonJS/Babylon.js · error
ProjectFile: Invalid project bundle — missing project.json
Error message
ProjectFile: Invalid project bundle — missing project.json
What it means
LoadProjectFileAsync unzips a project bundle and expects an entry literally named 'project.json' at the archive root. If the zip contains no such entry, the bundle is considered invalid and the promise rejects with this error.
Source
Thrown at packages/dev/sharedUiComponents/src/projects/projectFile.ts:403
/**
* Loads a `.babylonproj` zip bundle into a scene. Extracts all files, creates
* blob URLs for bundled assets, and loads the project through SAM.
*
* @param scene - The scene to load the project into.
* @param zipFile - The `.babylonproj` zip file to load.
*/
export async function LoadProjectFileAsync(scene: Scene, zipFile: File): Promise<void> {
const arrayBuffer = await zipFile.arrayBuffer();
const { unzip, strFromU8 } = await import("fflate");
const extracted = await new Promise<Record<string, Uint8Array>>((resolve, reject) => {
unzip(new Uint8Array(arrayBuffer), (err, data) => (err ? reject(err instanceof Error ? err : new Error(String(err))) : resolve(data)));
});
// Parse project.json
const projectJsonBytes = extracted["project.json"];
if (!projectJsonBytes) {
throw new Error("ProjectFile: Invalid project bundle — missing project.json");
}
const projectJson = JSON.parse(strFromU8(projectJsonBytes));
// Create blob URLs for all bundled files and rewrite asset URLs
for (const [, entry] of Object.entries(projectJson.assets as Record<string, { url: string }>)) {
const filename = entry.url;
const fileBytes = extracted[filename];
if (fileBytes) {
const mimeType = GuessMimeType(filename);
// Use a named File so LoadAssetContainerAsync can detect the
// format from the filename (blob URLs alone have no extension).
const file = new File([fileBytes as BlobPart], filename, { type: mimeType });
const blobUrl = URL.createObjectURL(file);
entry.url = blobUrl;
}
// If no file found in zip, assume the URL is a remote reference — leave it as-is
}
View on GitHub (pinned to 0592b347b8)
Solutions
- Re-create the zip so project.json sits at the archive root (select the folder's CONTENTS, not the folder itself).
- Verify the bundle contents by listing the zip entries before loading.
- Ensure you downloaded the project bundle (not a partial assets archive).
- Re-export the project from the editor to produce a valid bundle.
Example fix
// before (zip layout) myProject/project.json myProject/assets/... // after (zip layout) project.json assets/...
Defensive patterns
Strategy: validation
Validate before calling
const zip = await JSZip.loadAsync(arrayBuffer);
if (!zip.file('project.json')) {
throw new Error('bundle is missing root project.json');
}
await LoadProjectFileAsync(arrayBuffer, ...); Type guard
function bundleHasManifest(entries: Record<string, Uint8Array>): entries is Record<string, Uint8Array> & { 'project.json': Uint8Array } {
return Boolean(entries['project.json']);
} Try / catch
try {
await LoadProjectFileAsync(buffer, name);
} catch (e) {
if (e instanceof Error && e.message.includes('missing project.json')) {
// prompt user to re-upload a valid project bundle
} else throw e;
} Prevention
- Zip the folder CONTENTS so project.json is at the archive root, not nested.
- List zip entries and assert project.json exists before loading.
- Standardize on a bundler script instead of OS right-click compression.
When it happens
Trigger: Passing an ArrayBuffer of a zip that lacks a root-level project.json — e.g. the project.json is nested in a subfolder, the zip contains only assets, or the wrong file was uploaded.
Common situations: Re-zipping an extracted project with an extra wrapping folder (macOS Archive Utility / right-click compress), uploading the assets zip instead of the project bundle, or exporting from a tool that names the manifest differently.
Related errors
- Unable to deserialize NavMesh.
- Unable to deserialize TileCache.
- Could not find signal input with name ${name} in block ${cla
- Could not find signal output with name ${name} in block ${cl
- Snippet ${snippetId} does not contain a flow graph
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/31da952cbd6c504b.
Report an issue: GitHub.