Flow-Launcher/Flow.Launcher · error · FileNotFoundException

The zip file does not contain a plugin.json file.

Error message

The zip file does not contain a plugin.json file.

What it means

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.

Solutions

  1. Open the .zip and confirm plugin.json exists at the archive root (not nested).
  2. Ensure the manifest is named exactly plugin.json (lowercase).
  3. Re-download the plugin from its official source in case the archive is a non-plugin zip.
  4. If you are the plugin author, adjust your build/release script to put plugin.json at the zip root.

Example fix

// before — only matches the exact root-level name
var pluginJsonEntry = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json")
    ?? throw new FileNotFoundException("The zip file does not contain a plugin.json file.");

// after — search any depth and report what was found
var pluginJsonEntry = archive.Entries.FirstOrDefault(x =>
    string.Equals(x.Name, "plugin.json", StringComparison.OrdinalIgnoreCase));
if (pluginJsonEntry is null)
{
    PublicApi.Instance.ShowMsgError($"No plugin.json found. Entries: {string.Join(", ", archive.Entries.Select(e => e.FullName))}");
    return;
}
Defensive patterns

Strategy: validation

Validate before calling

using var archive = ZipFile.OpenRead(filePath);
bool hasPluginJson = archive.Entries.Any(e =>
    string.Equals(e.Name, "plugin.json", StringComparison.OrdinalIgnoreCase));
if (!hasPluginJson) { ShowMsgError("plugin.json missing at zip root"); return; }

Type guard

null

Try / catch

try { plugin = JsonSerializer.Deserialize<UserPlugin>(stream); }
catch (FileNotFoundException ex) when (ex.Message.Contains("plugin.json"))
{ PublicApi.Instance.ShowMsgError(Localize.ZipFileNotHavePluginJson()); return; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Flow-Launcher/Flow.Launcher@7fc63b07bb (2026-08-13). Data as JSON: /api/errors/804a99ff8b7833cd. Report an issue: GitHub.

Appendix: source

Thrown at Flow.Launcher.Core/Plugin/PluginInstaller.cs:118

            PublicApi.Instance.ShowMsg(
                Localize.installbtn(),
                Localize.InstallSuccessNoRestart(newPlugin.Name));
        }
    }

    /// <summary>
    /// Installs a plugin from a local zip file and restarts the application if required by settings. Validates the zip and prompts user for confirmation.
    /// </summary>
    /// <param name="filePath">The path to the plugin zip file.</param>
    /// <returns>A Task representing the asynchronous install operation.</returns>
    public static async Task InstallPluginAndCheckRestartAsync(string filePath)
    {
        UserPlugin plugin;
        try
        {
            using ZipArchive archive = ZipFile.OpenRead(filePath);
            var pluginJsonEntry = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json") ??
                throw new FileNotFoundException("The zip file does not contain a plugin.json file.");

            using Stream stream = pluginJsonEntry.Open();
            plugin = JsonSerializer.Deserialize<UserPlugin>(stream);
            plugin.IcoPath = "Images\\zipfolder.png";
            plugin.LocalInstallPath = filePath;
        }
        catch (Exception e)
        {
            PublicApi.Instance.LogException(ClassName, "Failed to validate zip file", e);
            PublicApi.Instance.ShowMsgError(Localize.ZipFileNotHavePluginJson());
            return;
        }

        if (PublicApi.Instance.PluginModified(plugin.ID))
        {
            PublicApi.Instance.ShowMsgError(Localize.pluginModifiedAlreadyTitle(plugin.Name),
                Localize.pluginModifiedAlreadyMessage());
            return;

View on GitHub (pinned to 7fc63b07bb)