diff --git a/docs/content/doc/packages/npm.en-us.md b/docs/content/doc/packages/npm.en-us.md
index 9ab4ac900c..122f306ee5 100644
--- a/docs/content/doc/packages/npm.en-us.md
+++ b/docs/content/doc/packages/npm.en-us.md
@@ -67,6 +67,26 @@ npm publish
 
 You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first.
 
+## Unpublish a package
+
+Delete a package by running the following command:
+
+```shell
+npm unpublish {package_name}[@{package_version}]
+```
+
+| Parameter         | Description |
+| ----------------- | ----------- |
+| `package_name`    | The package name. |
+| `package_version` | The package version. |
+
+For example:
+
+```shell
+npm unpublish @test/test_package
+npm unpublish @test/test_package@1.0.0
+```
+
 ## Install a package
 
 To install a package from the package registry, execute the following command:
@@ -113,6 +133,7 @@ The tag name must not be a valid version. All tag names which are parsable as a
 npm install
 npm ci
 npm publish
+npm unpublish
 npm dist-tag
 npm view
 ```
diff --git a/integrations/api_packages_npm_test.go b/integrations/api_packages_npm_test.go
index ad88ac5da6..23f13866bf 100644
--- a/integrations/api_packages_npm_test.go
+++ b/integrations/api_packages_npm_test.go
@@ -36,33 +36,36 @@ func TestPackageNpm(t *testing.T) {
 	packageDescription := "Test Description"
 
 	data := "H4sIAAAAAAAA/ytITM5OTE/VL4DQelnF+XkMVAYGBgZmJiYK2MRBwNDcSIHB2NTMwNDQzMwAqA7IMDUxA9LUdgg2UFpcklgEdAql5kD8ogCnhwio5lJQUMpLzE1VslJQcihOzi9I1S9JLS7RhSYIJR2QgrLUouLM/DyQGkM9Az1D3YIiqExKanFyUWZBCVQ2BKhVwQVJDKwosbQkI78IJO/tZ+LsbRykxFXLNdA+HwWjYBSMgpENACgAbtAACAAA"
-	upload := `{
-		"_id": "` + packageName + `",
-		"name": "` + packageName + `",
-		"description": "` + packageDescription + `",
-		"dist-tags": {
-		  "` + packageTag + `": "` + packageVersion + `"
-		},
-		"versions": {
-		  "` + packageVersion + `": {
+
+	buildUpload := func(version string) string {
+		return `{
+			"_id": "` + packageName + `",
 			"name": "` + packageName + `",
-			"version": "` + packageVersion + `",
 			"description": "` + packageDescription + `",
-			"author": {
-			  "name": "` + packageAuthor + `"
+			"dist-tags": {
+			  "` + packageTag + `": "` + version + `"
 			},
-			"dist": {
-			  "integrity": "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg==",
-			  "shasum": "aaa7eaf852a948b0aa05afeda35b1badca155d90"
+			"versions": {
+			  "` + version + `": {
+				"name": "` + packageName + `",
+				"version": "` + version + `",
+				"description": "` + packageDescription + `",
+				"author": {
+				  "name": "` + packageAuthor + `"
+				},
+				"dist": {
+				  "integrity": "sha512-yA4FJsVhetynGfOC1jFf79BuS+jrHbm0fhh+aHzCQkOaOBXKf9oBnC4a6DnLLnEsHQDRLYd00cwj8sCXpC+wIg==",
+				  "shasum": "aaa7eaf852a948b0aa05afeda35b1badca155d90"
+				}
+			  }
+			},
+			"_attachments": {
+			  "` + packageName + `-` + version + `.tgz": {
+				"data": "` + data + `"
+			  }
 			}
-		  }
-		},
-		"_attachments": {
-		  "` + packageName + `-` + packageVersion + `.tgz": {
-			"data": "` + data + `"
-		  }
-		}
-	  }`
+		  }`
+	}
 
 	root := fmt.Sprintf("/api/packages/%s/npm/%s", user.Name, url.QueryEscape(packageName))
 	tagsRoot := fmt.Sprintf("/api/packages/%s/npm/-/package/%s/dist-tags", user.Name, url.QueryEscape(packageName))
@@ -71,7 +74,7 @@ func TestPackageNpm(t *testing.T) {
 	t.Run("Upload", func(t *testing.T) {
 		defer PrintCurrentTest(t)()
 
-		req := NewRequestWithBody(t, "PUT", root, strings.NewReader(upload))
+		req := NewRequestWithBody(t, "PUT", root, strings.NewReader(buildUpload(packageVersion)))
 		req = addTokenAuthHeader(req, token)
 		MakeRequest(t, req, http.StatusCreated)
 
@@ -103,7 +106,7 @@ func TestPackageNpm(t *testing.T) {
 	t.Run("UploadExists", func(t *testing.T) {
 		defer PrintCurrentTest(t)()
 
-		req := NewRequestWithBody(t, "PUT", root, strings.NewReader(upload))
+		req := NewRequestWithBody(t, "PUT", root, strings.NewReader(buildUpload(packageVersion)))
 		req = addTokenAuthHeader(req, token)
 		MakeRequest(t, req, http.StatusBadRequest)
 	})
@@ -219,4 +222,57 @@ func TestPackageNpm(t *testing.T) {
 		test(t, http.StatusOK, "dummy")
 		test(t, http.StatusOK, packageTag2)
 	})
+
+	t.Run("Delete", func(t *testing.T) {
+		defer PrintCurrentTest(t)()
+
+		req := NewRequestWithBody(t, "PUT", root, strings.NewReader(buildUpload(packageVersion+"-dummy")))
+		req = addTokenAuthHeader(req, token)
+		MakeRequest(t, req, http.StatusCreated)
+
+		req = NewRequest(t, "PUT", root+"/-rev/dummy")
+		MakeRequest(t, req, http.StatusUnauthorized)
+
+		req = NewRequest(t, "PUT", root+"/-rev/dummy")
+		req = addTokenAuthHeader(req, token)
+		MakeRequest(t, req, http.StatusOK)
+
+		t.Run("Version", func(t *testing.T) {
+			defer PrintCurrentTest(t)()
+
+			pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm)
+			assert.NoError(t, err)
+			assert.Len(t, pvs, 2)
+
+			req := NewRequest(t, "DELETE", fmt.Sprintf("%s/-/%s/%s/-rev/dummy", root, packageVersion, filename))
+			MakeRequest(t, req, http.StatusUnauthorized)
+
+			req = NewRequest(t, "DELETE", fmt.Sprintf("%s/-/%s/%s/-rev/dummy", root, packageVersion, filename))
+			req = addTokenAuthHeader(req, token)
+			MakeRequest(t, req, http.StatusOK)
+
+			pvs, err = packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm)
+			assert.NoError(t, err)
+			assert.Len(t, pvs, 1)
+		})
+
+		t.Run("Full", func(t *testing.T) {
+			defer PrintCurrentTest(t)()
+
+			pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm)
+			assert.NoError(t, err)
+			assert.Len(t, pvs, 1)
+
+			req := NewRequest(t, "DELETE", root+"/-rev/dummy")
+			MakeRequest(t, req, http.StatusUnauthorized)
+
+			req = NewRequest(t, "DELETE", root+"/-rev/dummy")
+			req = addTokenAuthHeader(req, token)
+			MakeRequest(t, req, http.StatusOK)
+
+			pvs, err = packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeNpm)
+			assert.NoError(t, err)
+			assert.Len(t, pvs, 0)
+		})
+	})
 }
diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go
index 84bdce30fa..39ba41cdfb 100644
--- a/routers/api/packages/api.go
+++ b/routers/api/packages/api.go
@@ -198,12 +198,26 @@ func Routes() *web.Route {
 			r.Group("/@{scope}/{id}", func() {
 				r.Get("", npm.PackageMetadata)
 				r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage)
-				r.Get("/-/{version}/{filename}", npm.DownloadPackageFile)
+				r.Group("/-/{version}/{filename}", func() {
+					r.Get("", npm.DownloadPackageFile)
+					r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion)
+				})
+				r.Group("/-rev/{revision}", func() {
+					r.Delete("", npm.DeletePackage)
+					r.Put("", npm.DeletePreview)
+				}, reqPackageAccess(perm.AccessModeWrite))
 			})
 			r.Group("/{id}", func() {
 				r.Get("", npm.PackageMetadata)
 				r.Put("", reqPackageAccess(perm.AccessModeWrite), npm.UploadPackage)
-				r.Get("/-/{version}/{filename}", npm.DownloadPackageFile)
+				r.Group("/-/{version}/{filename}", func() {
+					r.Get("", npm.DownloadPackageFile)
+					r.Delete("/-rev/{revision}", reqPackageAccess(perm.AccessModeWrite), npm.DeletePackageVersion)
+				})
+				r.Group("/-rev/{revision}", func() {
+					r.Delete("", npm.DeletePackage)
+					r.Put("", npm.DeletePreview)
+				}, reqPackageAccess(perm.AccessModeWrite))
 			})
 			r.Group("/-/package/@{scope}/{id}/dist-tags", func() {
 				r.Get("", npm.ListPackageTags)
diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go
index 152edc681a..d5ba70f964 100644
--- a/routers/api/packages/npm/npm.go
+++ b/routers/api/packages/npm/npm.go
@@ -164,6 +164,63 @@ func UploadPackage(ctx *context.Context) {
 	ctx.Status(http.StatusCreated)
 }
 
+// DeletePreview does nothing
+// The client tells the server what package version it knows about after deleting a version.
+func DeletePreview(ctx *context.Context) {
+	ctx.Status(http.StatusOK)
+}
+
+// DeletePackageVersion deletes the package version
+func DeletePackageVersion(ctx *context.Context) {
+	packageName := packageNameFromParams(ctx)
+	packageVersion := ctx.Params("version")
+
+	err := packages_service.RemovePackageVersionByNameAndVersion(
+		ctx.Doer,
+		&packages_service.PackageInfo{
+			Owner:       ctx.Package.Owner,
+			PackageType: packages_model.TypeNpm,
+			Name:        packageName,
+			Version:     packageVersion,
+		},
+	)
+	if err != nil {
+		if err == packages_model.ErrPackageNotExist {
+			apiError(ctx, http.StatusNotFound, err)
+			return
+		}
+		apiError(ctx, http.StatusInternalServerError, err)
+		return
+	}
+
+	ctx.Status(http.StatusOK)
+}
+
+// DeletePackage deletes the package and all versions
+func DeletePackage(ctx *context.Context) {
+	packageName := packageNameFromParams(ctx)
+
+	pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNpm, packageName)
+	if err != nil {
+		apiError(ctx, http.StatusInternalServerError, err)
+		return
+	}
+
+	if len(pvs) == 0 {
+		apiError(ctx, http.StatusNotFound, err)
+		return
+	}
+
+	for _, pv := range pvs {
+		if err := packages_service.RemovePackageVersion(ctx.Doer, pv); err != nil {
+			apiError(ctx, http.StatusInternalServerError, err)
+			return
+		}
+	}
+
+	ctx.Status(http.StatusOK)
+}
+
 // ListPackageTags returns all tags for a package
 func ListPackageTags(ctx *context.Context) {
 	packageName := packageNameFromParams(ctx)