diff --git a/core/external_metadata.go b/core/external_metadata.go
index 1d7b8825a..d15fb8ea5 100644
--- a/core/external_metadata.go
+++ b/core/external_metadata.go
@@ -9,7 +9,6 @@ import (
 
 	"github.com/Masterminds/squirrel"
 	"github.com/kennygrant/sanitize"
-	"github.com/microcosm-cc/bluemonday"
 	"github.com/navidrome/navidrome/consts"
 	"github.com/navidrome/navidrome/core/agents"
 	_ "github.com/navidrome/navidrome/core/agents/lastfm"
@@ -284,8 +283,7 @@ func (e *externalMetadata) callGetBiography(ctx context.Context, agent agents.Ar
 	if bio == "" || err != nil {
 		return
 	}
-	policy := bluemonday.UGCPolicy()
-	bio = policy.Sanitize(bio)
+	bio = utils.SanitizeText(bio)
 	bio = strings.ReplaceAll(bio, "\n", " ")
 	artist.Biography = strings.ReplaceAll(bio, "<a ", "<a target='_blank' ")
 }
diff --git a/db/migration/20211026191915_unescape_lyrics_and_comments.go b/db/migration/20211026191915_unescape_lyrics_and_comments.go
new file mode 100644
index 000000000..186383212
--- /dev/null
+++ b/db/migration/20211026191915_unescape_lyrics_and_comments.go
@@ -0,0 +1,46 @@
+package migrations
+
+import (
+	"database/sql"
+
+	"github.com/navidrome/navidrome/log"
+	"github.com/navidrome/navidrome/utils"
+	"github.com/pressly/goose"
+)
+
+func init() {
+	goose.AddMigration(upUnescapeLyricsAndComments, downUnescapeLyricsAndComments)
+}
+
+func upUnescapeLyricsAndComments(tx *sql.Tx) error {
+	rows, err := tx.Query(`select id, comment, lyrics, title from media_file`)
+	if err != nil {
+		return err
+	}
+	defer rows.Close()
+
+	stmt, err := tx.Prepare("update media_file set comment = ?, lyrics = ? where id = ?")
+	if err != nil {
+		return err
+	}
+
+	var id, comment, lyrics, title string
+	for rows.Next() {
+		err = rows.Scan(&id, &comment, &lyrics, &title)
+		if err != nil {
+			return err
+		}
+
+		comment = utils.SanitizeText(comment)
+		lyrics = utils.SanitizeText(lyrics)
+		_, err = stmt.Exec(comment, lyrics, id)
+		if err != nil {
+			log.Error("Error unescaping media_file's lyrics and comments", "title", title, "id", id, err)
+		}
+	}
+	return rows.Err()
+}
+
+func downUnescapeLyricsAndComments(tx *sql.Tx) error {
+	return nil
+}
diff --git a/scanner/mapping.go b/scanner/mapping.go
index 2930b0eea..f6501923a 100644
--- a/scanner/mapping.go
+++ b/scanner/mapping.go
@@ -9,7 +9,6 @@ import (
 	"time"
 
 	"github.com/kennygrant/sanitize"
-	"github.com/microcosm-cc/bluemonday"
 	"github.com/navidrome/navidrome/conf"
 	"github.com/navidrome/navidrome/consts"
 	"github.com/navidrome/navidrome/model"
@@ -19,14 +18,12 @@ import (
 
 type mediaFileMapper struct {
 	rootFolder string
-	policy     *bluemonday.Policy
 	genres     model.GenreRepository
 }
 
 func newMediaFileMapper(rootFolder string, genres model.GenreRepository) *mediaFileMapper {
 	return &mediaFileMapper{
 		rootFolder: rootFolder,
-		policy:     bluemonday.UGCPolicy(),
 		genres:     genres,
 	}
 }
@@ -71,8 +68,8 @@ func (s mediaFileMapper) toMediaFile(md metadata.Tags) model.MediaFile {
 	mf.MbzAlbumArtistID = md.MbzAlbumArtistID()
 	mf.MbzAlbumType = md.MbzAlbumType()
 	mf.MbzAlbumComment = md.MbzAlbumComment()
-	mf.Comment = s.policy.Sanitize(md.Comment())
-	mf.Lyrics = s.policy.Sanitize(md.Lyrics())
+	mf.Comment = utils.SanitizeText(md.Comment())
+	mf.Lyrics = utils.SanitizeText(md.Lyrics())
 	mf.Bpm = md.Bpm()
 	mf.CreatedAt = time.Now()
 	mf.UpdatedAt = md.ModificationTime()
diff --git a/server/serve_index.go b/server/serve_index.go
index 424f2ffd6..26abc346b 100644
--- a/server/serve_index.go
+++ b/server/serve_index.go
@@ -8,11 +8,11 @@ import (
 	"net/http"
 	"strings"
 
-	"github.com/microcosm-cc/bluemonday"
 	"github.com/navidrome/navidrome/conf"
 	"github.com/navidrome/navidrome/consts"
 	"github.com/navidrome/navidrome/log"
 	"github.com/navidrome/navidrome/model"
+	"github.com/navidrome/navidrome/utils"
 )
 
 // Injects the config in the `index.html` template
@@ -26,14 +26,13 @@ func serveIndex(ds model.DataStore, fs fs.FS) http.HandlerFunc {
 			http.NotFound(w, r)
 			return
 		}
-		policy := bluemonday.UGCPolicy()
 		appConfig := map[string]interface{}{
 			"version":                 consts.Version(),
 			"firstTime":               firstTime,
 			"variousArtistsId":        consts.VariousArtistsID,
-			"baseURL":                 policy.Sanitize(strings.TrimSuffix(conf.Server.BaseURL, "/")),
-			"loginBackgroundURL":      policy.Sanitize(conf.Server.UILoginBackgroundURL),
-			"welcomeMessage":          policy.Sanitize(conf.Server.UIWelcomeMessage),
+			"baseURL":                 utils.SanitizeText(strings.TrimSuffix(conf.Server.BaseURL, "/")),
+			"loginBackgroundURL":      utils.SanitizeText(conf.Server.UILoginBackgroundURL),
+			"welcomeMessage":          utils.SanitizeText(conf.Server.UIWelcomeMessage),
 			"enableTranscodingConfig": conf.Server.EnableTranscodingConfig,
 			"enableDownloads":         conf.Server.EnableDownloads,
 			"enableFavourites":        conf.Server.EnableFavourites,
diff --git a/utils/sanitize_strings.go b/utils/sanitize_strings.go
index e64c5654e..93a36ebb4 100644
--- a/utils/sanitize_strings.go
+++ b/utils/sanitize_strings.go
@@ -1,11 +1,13 @@
 package utils
 
 import (
+	"html"
 	"regexp"
 	"sort"
 	"strings"
 
 	"github.com/kennygrant/sanitize"
+	"github.com/microcosm-cc/bluemonday"
 )
 
 var quotesRegex = regexp.MustCompile("[“”‘’'\"\\[\\(\\{\\]\\)\\}]")
@@ -29,3 +31,10 @@ func SanitizeStrings(text ...string) string {
 	sort.Strings(fullText)
 	return strings.Join(fullText, " ")
 }
+
+var policy = bluemonday.UGCPolicy()
+
+func SanitizeText(text string) string {
+	s := policy.Sanitize(text)
+	return html.UnescapeString(s)
+}