diff --git a/pkg/plugin/discovery/discovery.go b/pkg/plugin/discovery/discovery.go
index 0b4e887bfd1819c47df4b26fea02599a554d6e88..dc597de4cf8f2f4b056075bf264315623cf831b8 100644
--- a/pkg/plugin/discovery/discovery.go
+++ b/pkg/plugin/discovery/discovery.go
@@ -127,7 +127,6 @@ func (d *Discovery) FindPluginsWithBatchResolver(resolverName string, pInfos []*
 	if err != nil {
 		return err
 	}
-	// TODO
-	_ = batchDownloadInfo
-	return nil
+
+	return downloadBatchPlugins(missingPlugins, batchDownloadInfo, d.config.ShowProgress)
 }
diff --git a/pkg/plugin/discovery/download.go b/pkg/plugin/discovery/download.go
index 9ed85ba5db61f49c278de156ba1647d1fdc14fdb..f54d5680e68fe8481d75b41cd887e60be8d3b800 100644
--- a/pkg/plugin/discovery/download.go
+++ b/pkg/plugin/discovery/download.go
@@ -127,3 +127,78 @@ func downloadPlugin(pluginInfo *plugin.Info, downloadInfo *resolver.PluginDownlo
 	}
 	return targetFile, nil
 }
+
+//gocyclo:ignore
+func downloadBatchPlugins(pluginInfos []*plugin.Info, downloadInfo *resolver.BatchPluginDownloadInfo, showProgress bool) error {
+	req, err := grab.NewRequest(PluginDir, downloadInfo.URL)
+	if err != nil {
+		return err
+	}
+	if downloadInfo.Checksum != "" {
+		sum, decErr := hex.DecodeString(downloadInfo.Checksum)
+		if decErr != nil {
+			return fmt.Errorf("could not decode checksum: %w", decErr)
+		}
+		req.SetChecksum(sha256.New(), sum, true)
+	}
+
+	res := grab.DefaultClient.Do(req)
+	if showProgress {
+		showDownloadProgressBar("batched-plugins", res)
+	}
+	err = res.Err()
+	if err != nil {
+		return err
+	}
+	defer os.Remove(res.Filename)
+
+	tgzFile, err := os.Open(res.Filename)
+	if err != nil {
+		return err
+	}
+	defer tgzFile.Close()
+
+	gunzip, err := gzip.NewReader(tgzFile)
+	if err != nil {
+		return err
+	}
+	defer gunzip.Close()
+
+	tarReader := tar.NewReader(gunzip)
+	for {
+		header, tarErr := tarReader.Next()
+		if errors.Is(tarErr, io.EOF) {
+			break
+		}
+		if tarErr != nil {
+			return tarErr
+		}
+		if header.Typeflag != tar.TypeReg {
+			continue
+		}
+
+		outFileName := path.Join(PluginDir, header.Name)
+		outDirName := path.Dir(outFileName)
+		if err = os.MkdirAll(outDirName, 0o755); err != nil {
+			return err
+		}
+
+		outFile, oErr := os.OpenFile(outFileName, os.O_CREATE|os.O_WRONLY, 0o755)
+		if oErr != nil {
+			return oErr
+		}
+		_, cErr := io.Copy(outFile, tarReader)
+		_ = outFile.Close()
+		if cErr != nil {
+			return cErr
+		}
+
+		for _, pluginInfo := range pluginInfos {
+			if strings.HasPrefix(path.Join(PluginDir, header.Name), pluginInfo.PluginPath) {
+				pluginInfo.BinPath = outFileName
+			}
+		}
+
+	}
+	return nil
+}
diff --git a/pkg/plugin/discovery/local.go b/pkg/plugin/discovery/local.go
index 226a519481461a217ca7a41090ed19010cf96209..125a243787103e59734bb073aea9350073b47eac 100644
--- a/pkg/plugin/discovery/local.go
+++ b/pkg/plugin/discovery/local.go
@@ -17,7 +17,7 @@ const PluginDir = ".semrel"
 var osArchDir = runtime.GOOS + "_" + runtime.GOARCH
 
 func setAndEnsurePluginPath(pluginInfo *plugin.Info) error {
-	pluginPath := path.Join(PluginDir, osArchDir, pluginInfo.NormalizedName)
+	pluginPath := path.Join(PluginDir, osArchDir, pluginInfo.ShortNormalizedName)
 	if _, err := os.Stat(pluginPath); os.IsNotExist(err) {
 		err = os.MkdirAll(pluginPath, 0o755)
 		if err != nil {