odoo/odoo · error · UserError

No manifest found in '%(modules)s'. Can't import the zip fil

Error message

No manifest found in '%(modules)s'. Can't import the zip file.

What it means

After extracting manifests, _import_zipfile builds the set of top-level directories in the archive and compares it with the topological sort of modules that had a recognized manifest (__manifest__.py etc.). Any directory without a manifest makes the import fail, because Odoo would import arbitrary folders as code/data with no metadata. The message lists the offending top-level directories.

Source

Thrown at addons/base_import_module/models/ir_module.py:365

                dependencies = defaultdict(list)
                for mod_name, manifest in manifest_files:
                    _manifest_path = z.extract(manifest, module_dir)
                    terp = Manifest._from_path(opj(module_dir, mod_name), env=self.env)
                    if not terp:
                        continue
                    files_to_import = terp.get('data', []) + terp.get('init_xml', []) + terp.get('update_xml', [])
                    if with_demo:
                        files_to_import += terp.get('demo', [])
                    for filename in files_to_import:
                        if os.path.splitext(filename)[1].lower() not in ('.xml', '.csv', '.sql'):
                            continue
                        module_data_files[mod_name].append('%s/%s' % (mod_name, filename))
                    dependencies[mod_name] = terp.get('depends', [])

                dirs = {d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))}
                sorted_dirs = topological_sort(dependencies)
                if wrong_modules := dirs.difference(sorted_dirs):
                    raise UserError(_(
                        "No manifest found in '%(modules)s'. Can't import the zip file.",
                        modules=", ".join(wrong_modules)
                    ))

                for file in z.infolist():
                    filename = file.filename
                    mod_name = filename.split('/')[0]
                    is_data_file = filename in module_data_files[mod_name]
                    is_static = filename.startswith('%s/static' % mod_name)
                    is_translation = filename.startswith('%s/i18n' % mod_name) and filename.endswith('.po')
                    if is_data_file or is_static or is_translation:
                        z.extract(file, module_dir)

                for mod_name in sorted_dirs:
                    module_names.append(mod_name)
                    try:
                        # assert mod_name.startswith('theme_')
                        path = opj(module_dir, mod_name)

View on GitHub (pinned to 1e661df964)

Solutions

  1. Re-zip so each top-level folder in the archive is a module containing __manifest__.py at its root
  2. Remove junk entries: __MACOSX, .DS_Store, .git, build dirs
  3. Fix manifest filename to exactly __manifest__.py (legacy __openerp__.py also accepted)

Example fix

# before (archive root)
# parent_dir/my_module/__manifest__.py  -> raises, top dir 'parent_dir' has no manifest

# after
zip -r my_module.zip my_module/
# archive root: my_module/__manifest__.py, my_module/models/...
Defensive patterns

Strategy: validation

Validate before calling

MANIFEST_NAMES = {'__manifest__.py', '__openerp__.py'}

def zip_layout_is_valid(data):
    with zipfile.ZipFile(io.BytesIO(data)) as z:
        tops = {n.split('/')[0] for n in z.namelist() if '/' in n}
        manifests = {n.split('/')[0] for n in z.namelist()
                     if n.count('/') == 1 and n.split('/')[1] in MANIFEST_NAMES}
    return tops <= manifests

Prevention

When it happens

Trigger: The ZIP contains a top-level folder without __manifest__.py/__openerp__.py — e.g. zipping the parent directory so 'my_module/' sits beside '__MACOSX/', '.git/', or stray folders; or a manifest misnamed so it is not in MANIFEST_NAMES.

Common situations: 'zip -r module.zip parent_dir/' instead of zipping the module folder itself; macOS adds __MACOSX; developer renames manifest to manifest.py; nested module layout module/module/.

Related errors


AI-assisted analysis of odoo/odoo@1e661df964 (2026-08-15). Data as JSON: /api/errors/30930100f3e1b2f8. Report an issue: GitHub.