{"record":{"id":"30930100f3e1b2f8","repo":"odoo/odoo","slug":"no-manifest-found-in-modules-s-can-t-import-t","errorCode":null,"errorMessage":"No manifest found in '%(modules)s'. Can't import the zip file.","messagePattern":"No manifest found in '(.+?)'\\. Can't import the zip file\\.","errorType":"exception","errorClass":"UserError","httpStatus":null,"severity":"error","filePath":"addons/base_import_module/models/ir_module.py","lineNumber":365,"sourceCode":"                dependencies = defaultdict(list)\n                for mod_name, manifest in manifest_files:\n                    _manifest_path = z.extract(manifest, module_dir)\n                    terp = Manifest._from_path(opj(module_dir, mod_name), env=self.env)\n                    if not terp:\n                        continue\n                    files_to_import = terp.get('data', []) + terp.get('init_xml', []) + terp.get('update_xml', [])\n                    if with_demo:\n                        files_to_import += terp.get('demo', [])\n                    for filename in files_to_import:\n                        if os.path.splitext(filename)[1].lower() not in ('.xml', '.csv', '.sql'):\n                            continue\n                        module_data_files[mod_name].append('%s/%s' % (mod_name, filename))\n                    dependencies[mod_name] = terp.get('depends', [])\n\n                dirs = {d for d in os.listdir(module_dir) if os.path.isdir(opj(module_dir, d))}\n                sorted_dirs = topological_sort(dependencies)\n                if wrong_modules := dirs.difference(sorted_dirs):\n                    raise UserError(_(\n                        \"No manifest found in '%(modules)s'. Can't import the zip file.\",\n                        modules=\", \".join(wrong_modules)\n                    ))\n\n                for file in z.infolist():\n                    filename = file.filename\n                    mod_name = filename.split('/')[0]\n                    is_data_file = filename in module_data_files[mod_name]\n                    is_static = filename.startswith('%s/static' % mod_name)\n                    is_translation = filename.startswith('%s/i18n' % mod_name) and filename.endswith('.po')\n                    if is_data_file or is_static or is_translation:\n                        z.extract(file, module_dir)\n\n                for mod_name in sorted_dirs:\n                    module_names.append(mod_name)\n                    try:\n                        # assert mod_name.startswith('theme_')\n                        path = opj(module_dir, mod_name)","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/odoo/odoo/blob/1e661df964b1b264c9cef3ab28430d4785be3fda/addons/base_import_module/models/ir_module.py#L347-L383","documentation":"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.","triggerScenarios":"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.","commonSituations":"'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/.","solutions":["Re-zip so each top-level folder in the archive is a module containing __manifest__.py at its root","Remove junk entries: __MACOSX, .DS_Store, .git, build dirs","Fix manifest filename to exactly __manifest__.py (legacy __openerp__.py also accepted)"],"exampleFix":"# before (archive root)\n# parent_dir/my_module/__manifest__.py  -> raises, top dir 'parent_dir' has no manifest\n\n# after\nzip -r my_module.zip my_module/\n# archive root: my_module/__manifest__.py, my_module/models/...","handlingStrategy":"validation","validationCode":"MANIFEST_NAMES = {'__manifest__.py', '__openerp__.py'}\n\ndef zip_layout_is_valid(data):\n    with zipfile.ZipFile(io.BytesIO(data)) as z:\n        tops = {n.split('/')[0] for n in z.namelist() if '/' in n}\n        manifests = {n.split('/')[0] for n in z.namelist()\n                     if n.count('/') == 1 and n.split('/')[1] in MANIFEST_NAMES}\n    return tops <= manifests","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Zip from inside the parent: cd parent && zip -r mod.zip mod/","Exclude __MACOSX, .DS_Store, .git from archives","Name the manifest exactly __manifest__.py"],"tags":["odoo","module-import","zip","manifest","packaging","user-error"],"backgroundTag":null,"analyzedSha":"1e661df964b1b264c9cef3ab28430d4785be3fda","analyzedAt":"2026-08-15T05:22:16.142Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}