{"record":{"id":"804a99ff8b7833cd","repo":"Flow-Launcher/Flow.Launcher","slug":"the-zip-file-does-not-contain-a-plugin-json-file","errorCode":null,"errorMessage":"The zip file does not contain a plugin.json file.","messagePattern":"The zip file does not contain a plugin\\.json file\\.","errorType":"exception","errorClass":"FileNotFoundException","httpStatus":null,"severity":"error","filePath":"Flow.Launcher.Core/Plugin/PluginInstaller.cs","lineNumber":118,"sourceCode":"            PublicApi.Instance.ShowMsg(\n                Localize.installbtn(),\n                Localize.InstallSuccessNoRestart(newPlugin.Name));\n        }\n    }\n\n    /// <summary>\n    /// Installs a plugin from a local zip file and restarts the application if required by settings. Validates the zip and prompts user for confirmation.\n    /// </summary>\n    /// <param name=\"filePath\">The path to the plugin zip file.</param>\n    /// <returns>A Task representing the asynchronous install operation.</returns>\n    public static async Task InstallPluginAndCheckRestartAsync(string filePath)\n    {\n        UserPlugin plugin;\n        try\n        {\n            using ZipArchive archive = ZipFile.OpenRead(filePath);\n            var pluginJsonEntry = archive.Entries.FirstOrDefault(x => x.Name == \"plugin.json\") ??\n                throw new FileNotFoundException(\"The zip file does not contain a plugin.json file.\");\n\n            using Stream stream = pluginJsonEntry.Open();\n            plugin = JsonSerializer.Deserialize<UserPlugin>(stream);\n            plugin.IcoPath = \"Images\\\\zipfolder.png\";\n            plugin.LocalInstallPath = filePath;\n        }\n        catch (Exception e)\n        {\n            PublicApi.Instance.LogException(ClassName, \"Failed to validate zip file\", e);\n            PublicApi.Instance.ShowMsgError(Localize.ZipFileNotHavePluginJson());\n            return;\n        }\n\n        if (PublicApi.Instance.PluginModified(plugin.ID))\n        {\n            PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name),\n                Localize.pluginModifiedAlreadyMessage());\n            return;","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/Flow-Launcher/Flow.Launcher/blob/7fc63b07bbaa10a0f0b2601487913fcba9ed24b0/Flow.Launcher.Core/Plugin/PluginInstaller.cs#L100-L136","documentation":"Thrown as FileNotFoundException when ZipFile.OpenRead(filePath) succeeds but no archive entry has the exact name 'plugin.json' (case-sensitive Name match). It is caught immediately by the surrounding try/catch which logs 'Failed to validate zip file', shows a localized error, and returns — so this is a soft, user-facing failure rather than a crash.","triggerScenarios":"User selects a .zip that is not a Flow Launcher plugin (random archive, wrong packaging); plugin.json is present but inside a nested subfolder so its entry Name differs; the file is named Plugin.json (different case) on a case-sensitive packaging; the zip is corrupt and entries aren't enumerable as expected.","commonSituations":"Developers packaging their plugin and forgetting to flatten the directory structure so plugin.json lands under a subfolder; users trying to install a zip downloaded from the wrong source; plugin built on a case-sensitive system that renamed the manifest.","solutions":["Open the .zip and confirm plugin.json exists at the archive root (not nested).","Ensure the manifest is named exactly plugin.json (lowercase).","Re-download the plugin from its official source in case the archive is a non-plugin zip.","If you are the plugin author, adjust your build/release script to put plugin.json at the zip root."],"exampleFix":"// before — only matches the exact root-level name\nvar pluginJsonEntry = archive.Entries.FirstOrDefault(x => x.Name == \"plugin.json\")\n    ?? throw new FileNotFoundException(\"The zip file does not contain a plugin.json file.\");\n\n// after — search any depth and report what was found\nvar pluginJsonEntry = archive.Entries.FirstOrDefault(x =>\n    string.Equals(x.Name, \"plugin.json\", StringComparison.OrdinalIgnoreCase));\nif (pluginJsonEntry is null)\n{\n    PublicApi.Instance.ShowMsgError($\"No plugin.json found. Entries: {string.Join(\", \", archive.Entries.Select(e => e.FullName))}\");\n    return;\n}","handlingStrategy":"validation","validationCode":"using var archive = ZipFile.OpenRead(filePath);\nbool hasPluginJson = archive.Entries.Any(e =>\n    string.Equals(e.Name, \"plugin.json\", StringComparison.OrdinalIgnoreCase));\nif (!hasPluginJson) { ShowMsgError(\"plugin.json missing at zip root\"); return; }","typeGuard":"null","tryCatchPattern":"try { plugin = JsonSerializer.Deserialize<UserPlugin>(stream); }\ncatch (FileNotFoundException ex) when (ex.Message.Contains(\"plugin.json\"))\n{ PublicApi.Instance.ShowMsgError(Localize.ZipFileNotHavePluginJson()); return; }","preventionTips":["When packaging a plugin, flatten the directory so plugin.json sits at the zip root.","Always name the manifest exactly plugin.json (lowercase).","After zipping, open the archive and confirm the manifest's location.","Download plugins only from the official manifest to avoid non-plugin zips."],"tags":["plugin-install","zip","validation","packaging"],"backgroundTag":null,"analyzedSha":"7fc63b07bbaa10a0f0b2601487913fcba9ed24b0","analyzedAt":"2026-08-13T14:54:20.914Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}