diff --git a/source/dub/commandline.d b/source/dub/commandline.d
index 24ffb9e..84e28d3 100644
--- a/source/dub/commandline.d
+++ b/source/dub/commandline.d
@@ -116,6 +116,7 @@
 			new AddOverrideCommand,
 			new RemoveOverrideCommand,
 			new ListOverridesCommand,
+			new CleanCachesCommand,
 		)
 	];
 
@@ -1222,6 +1223,29 @@
 	}
 }
 
+/******************************************************************************/
+/* Cache cleanup                                                              */
+/******************************************************************************/
+
+class CleanCachesCommand : Command {
+	this()
+	{
+		this.name = "clean-caches";
+		this.argumentsPattern = "";
+		this.description = "Removes cached metadata";
+		this.helpText = [
+			"This command removes any cached metadata like the list of available packages and their latest version."
+		];
+	}
+
+	override void prepare(scope CommandArgs args) {}
+
+	override int execute(Dub dub, string[] free_args, string[] app_args)
+	{
+		dub.cleanCaches();
+		return 0;
+	}
+}
 
 /******************************************************************************/
 /* DUSTMITE                                                                   */
diff --git a/source/dub/dub.d b/source/dub/dub.d
index 5ba6df6..0450135 100644
--- a/source/dub/dub.d
+++ b/source/dub/dub.d
@@ -107,7 +107,7 @@
 
 		auto cacheDir = m_userDubPath ~ "cache/";
 		foreach (p; ps)
-			p.loadCache(cacheDir);
+			p.cacheOp(cacheDir, CacheOp.load);
 
 		m_packageSuppliers = ps;
 		m_packageManager = new PackageManager(m_userDubPath, m_systemDubPath);
@@ -127,7 +127,15 @@
 	{
 		auto cacheDir = m_userDubPath ~ "cache/";
 		foreach (p; m_packageSuppliers)
-			p.storeCache(cacheDir);
+			p.cacheOp(cacheDir, CacheOp.store);
+	}
+
+	/// cleans all metadata caches
+	void cleanCaches()
+	{
+		auto cacheDir = m_userDubPath ~ "cache/";
+		foreach (p; m_packageSuppliers)
+			p.cacheOp(cacheDir, CacheOp.clean);
 	}
 
 	@property void dryRun(bool v) { m_dryRun = v; }
diff --git a/source/dub/packagesupplier.d b/source/dub/packagesupplier.d
index 4c44e67..12e9b04 100644
--- a/source/dub/packagesupplier.d
+++ b/source/dub/packagesupplier.d
@@ -39,11 +39,15 @@
 	/// returns the metadata for the package
 	Json getPackageDescription(string packageId, Dependency dep, bool pre_release);
 
-	/// load caches to disk
-	void loadCache(Path cacheDir);
+	/// perform cache operation
+	void cacheOp(Path cacheDir, CacheOp op);
+}
 
-	/// persist caches to disk
-	void storeCache(Path cacheDir);
+/// operations on package supplier cache
+enum CacheOp {
+	load,
+	store,
+	clean,
 }
 
 class FileSystemPackageSupplier : PackageSupplier {
@@ -85,10 +89,7 @@
 		return jsonFromZip(filename, "dub.json");
 	}
 
-	void storeCache(Path cacheDir) {
-	}
-
-	void loadCache(Path cacheDir) {
+	void cacheOp(Path cacheDir, CacheOp op) {
 	}
 
 	private Path bestPackageFile(string packageId, Dependency dep, bool pre_release)
@@ -152,24 +153,29 @@
 		return getBestPackage(packageId, dep, pre_release);
 	}
 
-	void storeCache(Path cacheDir)
-	{
-		if (!m_metadataCacheDirty) return;
-
-		auto path = cacheDir ~ cacheFileName;
-		if (!cacheDir.existsFile())
-			mkdirRecurse(cacheDir.toNativeString());
-		// TODO: method is slow due to Json escaping
-		writeJsonFile(path, m_metadataCache.serializeToJson());
-		m_metadataCacheDirty = false;
-	}
-
-	void loadCache(Path cacheDir)
+	void cacheOp(Path cacheDir, CacheOp op)
 	{
 		auto path = cacheDir ~ cacheFileName;
-		if (!path.existsFile()) return;
+		final switch (op)
+		{
+		case CacheOp.store:
+			if (!m_metadataCacheDirty) return;
+			if (!cacheDir.existsFile())
+				mkdirRecurse(cacheDir.toNativeString());
+			// TODO: method is slow due to Json escaping
+			writeJsonFile(path, m_metadataCache.serializeToJson());
+			break;
 
-		deserializeJson(m_metadataCache, jsonFromFile(path));
+		case CacheOp.load:
+			if (!path.existsFile()) return;
+			deserializeJson(m_metadataCache, jsonFromFile(path));
+			break;
+
+		case CacheOp.clean:
+			if (path.existsFile()) removeFile(path);
+			m_metadataCache.destroy();
+			break;
+		}
 		m_metadataCacheDirty = false;
 	}