diff --git a/src/renderer/helpers/playlists.js b/src/renderer/helpers/playlists.js
index 5f53972f4b43d..6d8e2ed565bd4 100644
--- a/src/renderer/helpers/playlists.js
+++ b/src/renderer/helpers/playlists.js
@@ -5,6 +5,8 @@ export const SORT_BY_VALUES = {
   AuthorDescending: 'author_descending',
   VideoTitleAscending: 'video_title_ascending',
   VideoTitleDescending: 'video_title_descending',
+  VideoDurationAscending: 'video_duration_ascending',
+  VideoDurationDescending: 'video_duration_descending',
   Custom: 'custom'
 }
 
@@ -19,7 +21,9 @@ export function getSortedPlaylistItems(playlistItems, sortOrder, locale, reverse
     sortOrder === SORT_BY_VALUES.VideoTitleAscending ||
     sortOrder === SORT_BY_VALUES.VideoTitleDescending ||
     sortOrder === SORT_BY_VALUES.AuthorAscending ||
-    sortOrder === SORT_BY_VALUES.AuthorDescending
+    sortOrder === SORT_BY_VALUES.AuthorDescending ||
+    sortOrder === SORT_BY_VALUES.VideoDurationAscending ||
+    sortOrder === SORT_BY_VALUES.VideoDurationDescending
   ) {
     collator = new Intl.Collator([locale, 'en'])
   }
@@ -31,6 +35,19 @@ export function getSortedPlaylistItems(playlistItems, sortOrder, locale, reverse
   })
 }
 
+export function videoDurationPresent(video) {
+  if (typeof video.lengthSeconds !== 'number') { return false }
+
+  return !(isNaN(video.lengthSeconds) || video.lengthSeconds === 0)
+}
+
+export function videoDurationWithFallback(video) {
+  if (videoDurationPresent(video)) { return video.lengthSeconds }
+
+  // Fallback
+  return 0
+}
+
 function compareTwoPlaylistItems(a, b, sortOrder, collator) {
   switch (sortOrder) {
     case SORT_BY_VALUES.DateAddedNewest:
@@ -45,6 +62,12 @@ function compareTwoPlaylistItems(a, b, sortOrder, collator) {
       return collator.compare(a.author, b.author)
     case SORT_BY_VALUES.AuthorDescending:
       return collator.compare(b.author, a.author)
+    case SORT_BY_VALUES.VideoDurationAscending: {
+      return videoDurationWithFallback(a) - videoDurationWithFallback(b)
+    }
+    case SORT_BY_VALUES.VideoDurationDescending: {
+      return videoDurationWithFallback(b) - videoDurationWithFallback(a)
+    }
     default:
       console.error(`Unknown sortOrder: ${sortOrder}`)
       return 0
diff --git a/src/renderer/views/Playlist/Playlist.js b/src/renderer/views/Playlist/Playlist.js
index 8121a626f09b6..82faccfb8f912 100644
--- a/src/renderer/views/Playlist/Playlist.js
+++ b/src/renderer/views/Playlist/Playlist.js
@@ -20,9 +20,10 @@ import {
   getIconForSortPreference,
   setPublishedTimestampsInvidious,
   showToast,
+  deepCopy,
 } from '../../helpers/utils'
 import { invidiousGetPlaylistInfo, youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
-import { getSortedPlaylistItems, SORT_BY_VALUES } from '../../helpers/playlists'
+import { getSortedPlaylistItems, videoDurationPresent, videoDurationWithFallback, SORT_BY_VALUES } from '../../helpers/playlists'
 import packageDetails from '../../../../package.json'
 import { MOBILE_WIDTH_THRESHOLD, PLAYLIST_HEIGHT_FORCE_LIST_THRESHOLD } from '../../../constants'
 
@@ -74,6 +75,7 @@ export default defineComponent({
       getPlaylistInfoDebounce: function() {},
       playlistInEditMode: false,
       forceListView: false,
+      alreadyShownNotice: false,
 
       videoSearchQuery: '',
 
@@ -180,6 +182,13 @@ export default defineComponent({
       return this.sortOrder === SORT_BY_VALUES.Custom
     },
     sortedPlaylistItems: function () {
+      if (
+        this.sortOrder === SORT_BY_VALUES.VideoDurationAscending ||
+        this.sortOrder === SORT_BY_VALUES.VideoDurationDescending
+      ) {
+        const playlistItems = this.getPlaylistItemsWithDuration()
+        return getSortedPlaylistItems(playlistItems, this.sortOrder, this.currentLocale)
+      }
       return getSortedPlaylistItems(this.playlistItems, this.sortOrder, this.currentLocale)
     },
     visiblePlaylistItems: function () {
@@ -214,6 +223,10 @@ export default defineComponent({
             return this.$t('Playlist.Sort By.AuthorAscending')
           case SORT_BY_VALUES.AuthorDescending:
             return this.$t('Playlist.Sort By.AuthorDescending')
+          case SORT_BY_VALUES.VideoDurationAscending:
+            return this.$t('Playlist.Sort By.VideoDurationAscending')
+          case SORT_BY_VALUES.VideoDurationDescending:
+            return this.$t('Playlist.Sort By.VideoDurationDescending')
           default:
             console.error(`Unknown sort: ${k}`)
             return k
@@ -416,10 +429,40 @@ export default defineComponent({
 
       this.isLoading = false
     },
+
     showUserPlaylistNotFound() {
       showToast(this.$t('User Playlists.SinglePlaylistView.Toast.This playlist does not exist'))
     },
 
+    getPlaylistItemsWithDuration() {
+      const modifiedPlaylistItems = deepCopy(this.playlistItems)
+      let anyVideoMissingDuration = false
+      modifiedPlaylistItems.forEach(video => {
+        if (videoDurationPresent(video)) { return }
+
+        const videoHistory = this.$store.getters.getHistoryCacheById[video.videoId]
+        if (typeof videoHistory !== 'undefined') {
+          const fetchedLengthSeconds = videoDurationWithFallback(videoHistory)
+          video.lengthSeconds = fetchedLengthSeconds
+          // if the video duration is 0, it will be the fallback value, so mark it as missing a duration
+          if (fetchedLengthSeconds === 0) { anyVideoMissingDuration = true }
+        } else {
+          // Mark at least one video have no duration, show notice later
+          // Also assign fallback duration here
+          anyVideoMissingDuration = true
+          video.lengthSeconds = 0
+        }
+      })
+
+      // Show notice if not already shown before returning playlist items
+      if (anyVideoMissingDuration && !this.alreadyShownNotice) {
+        showToast(this.$t('User Playlists.SinglePlaylistView.Toast.This playlist has a video with a duration error'), 5000)
+        this.alreadyShownNotice = true
+      }
+
+      return modifiedPlaylistItems
+    },
+
     getNextPage: function () {
       switch (this.infoSource) {
         case 'local':
diff --git a/static/locales/en-GB.yaml b/static/locales/en-GB.yaml
index 995766999782f..b9269c166f5ee 100644
--- a/static/locales/en-GB.yaml
+++ b/static/locales/en-GB.yaml
@@ -174,6 +174,8 @@ User Playlists:
       This playlist is protected and cannot be removed.: This playlist is protected
         and cannot be removed.
       This playlist does not exist: This playlist does not exist
+
+      This playlist has a video with a duration error: This playlist contains at least one video that doesn't have a duration, it will be sorted as if their duration is zero.
       Playlist {playlistName} has been deleted.: Playlist {playlistName} has been
         deleted.
       Video has been removed: Video has been removed
@@ -1006,6 +1008,8 @@ Playlist:
     AuthorAscending: Author (A-Z)
     AuthorDescending: Author (Z-A)
     VideoTitleAscending: Title (A-Z)
+    VideoDurationAscending: Duration (Shortest first)
+    VideoDurationDescending: Duration (Longest first)
 Toggle Theatre Mode: 'Toggle Theatre Mode'
 Change Format:
   Change Media Formats: 'Change Media Formats'
diff --git a/static/locales/en-US.yaml b/static/locales/en-US.yaml
index f4366ef53ebd0..430a73dece2bc 100644
--- a/static/locales/en-US.yaml
+++ b/static/locales/en-US.yaml
@@ -236,6 +236,8 @@ User Playlists:
       Playlist {playlistName} is the new quick bookmark playlist.: Playlist {playlistName} is the new quick bookmark playlist.
 
       This playlist does not exist: This playlist does not exist
+
+      This playlist has a video with a duration error: This playlist contains at least one video that doesn't have a duration, it will be sorted as if their duration is zero.
   AddVideoPrompt:
     Select a playlist to add your N videos to: 'Select a playlist to add your video to | Select a playlist to add your {videoCount} videos to'
     N playlists selected: '{playlistCount} Selected'
@@ -928,6 +930,8 @@ Playlist:
     AuthorDescending: Author (Z-A)
     VideoTitleAscending: Title (A-Z)
     VideoTitleDescending: Title (Z-A)
+    VideoDurationAscending: Duration (Shortest first)
+    VideoDurationDescending: Duration (Longest first)
     Custom: Custom
 
 # On Video Watch Page