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.

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)

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.