usebruno/bruno · error · Error

Invalid collection: Neither bruno.json nor opencollection.ym

Error message

Invalid collection: Neither bruno.json nor opencollection.yml found in the ZIP file

What it means

Thrown during Bruno collection ZIP import. After extraction the importer picks the collection directory (the temp dir, or the single subdirectory when the archive wraps everything in one folder) and requires a `bruno.json` or `opencollection.yml` at its root. If neither marker file is present the ZIP is rejected as not a valid Bruno collection.

Source

Thrown at packages/bruno-electron/src/ipc/collection.js:2723

        validateNoExternalSymlinks(tempDir, tempDir);

        const extractedItems = fs.readdirSync(tempDir);
        let collectionDir = tempDir;

        if (extractedItems.length === 1) {
          const singleItem = path.join(tempDir, extractedItems[0]);
          const singleItemStat = fs.lstatSync(singleItem);
          if (singleItemStat.isDirectory() && !singleItemStat.isSymbolicLink()) {
            collectionDir = singleItem;
          }
        }

        const brunoJsonPath = path.join(collectionDir, 'bruno.json');
        const openCollectionYmlPath = path.join(collectionDir, 'opencollection.yml');

        if (!fs.existsSync(brunoJsonPath) && !fs.existsSync(openCollectionYmlPath)) {
          throw new Error('Invalid collection: Neither bruno.json nor opencollection.yml found in the ZIP file');
        }

        // Ensure config files are not symlinks
        if (fs.existsSync(brunoJsonPath) && fs.lstatSync(brunoJsonPath).isSymbolicLink()) {
          throw new Error('Security error: bruno.json cannot be a symbolic link');
        }
        if (fs.existsSync(openCollectionYmlPath) && fs.lstatSync(openCollectionYmlPath).isSymbolicLink()) {
          throw new Error('Security error: opencollection.yml cannot be a symbolic link');
        }

        let collectionName = 'Imported Collection';
        let brunoConfig = { name: collectionName, version: '1', type: 'collection', ignore: ['node_modules', '.git'] };
        if (fs.existsSync(openCollectionYmlPath)) {
          try {
            const content = fs.readFileSync(openCollectionYmlPath, 'utf8');
            const parsed = parseCollection(content, { format: 'yml' });
            brunoConfig = parsed.brunoConfig || brunoConfig;
            collectionName = brunoConfig.name || collectionName;

View on GitHub (pinned to 9bdd81c7bd)

Solutions

  1. Open the ZIP and confirm bruno.json (or opencollection.yml) is at the archive root or inside exactly one top-level folder.
  2. Re-create the archive from inside the collection directory (e.g. `zip -r coll.zip .`) so the config sits at the right level.
  3. If importing from another tool, run it through Bruno's import/conversion flow before zipping.
  4. Verify the marker file name and extension exactly: `bruno.json` / `opencollection.yml`.

Example fix

// before: archive layout
my-collection.zip
  nested-folder/
    my-collection/        <- bruno.json lives here (too deep)
      bruno.json
      get.bru
// after: config at most one folder below root
my-collection.zip
  my-collection/
    bruno.json
    get.bru
Defensive patterns

Strategy: validation

Validate before calling

const isValidCollectionZip = (zipPath) => {
  const AdmZip = require('adm-zip');
  const zip = new AdmZip(zipPath);
  const entries = zip.getEntries().map(e => e.entryName);
  return entries.some(e => /^([^/]+\/)?(bruno\.json|opencollection\.yml)$/.test(e));
};

Prevention

When it happens

Trigger: Importing a ZIP whose `bruno.json`/`opencollection.yml` sits more than one directory below the archive root (the single-item unwrap only descends one level); a ZIP that drops request files at the root with no config; importing an archive exported from another tool (Postman/Insomnia) without conversion; the marker file was renamed or is itself inside a second nested folder.

Common situations: User re-zips a folder incorrectly (zips the parent so an extra wrapper appears, or nests the collection one level too deep); editing an exported archive and breaking its layout; importing a non-Bruno archive directly.

Related errors


AI-assisted analysis of usebruno/bruno@9bdd81c7bd (2026-08-13). Data as JSON: /api/errors/0a7797feabfb2983. Report an issue: GitHub.