{"record":{"id":"084ea0907b05e04d","repo":"sampotts/plyr","slug":"missing-previewthumbnails-src-config-attribute","errorCode":null,"errorMessage":"Missing previewThumbnails.src config attribute","messagePattern":"Missing previewThumbnails\\.src config attribute","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/js/plugins/preview-thumbnails.js","lineNumber":141,"sourceCode":"      this.render();\n\n      // Check to see if thumb container size was specified manually in CSS\n      this.determineContainerAutoSizing();\n\n      // Set up listeners\n      this.listeners();\n\n      this.loaded = true;\n    });\n  };\n\n  // Download VTT files and parse them\n  getThumbnails = () => {\n    return new Promise((resolve) => {\n      const { src } = this.player.config.previewThumbnails;\n\n      if (is.empty(src)) {\n        throw new Error('Missing previewThumbnails.src config attribute');\n      }\n\n      // Resolve promise\n      const sortAndResolve = () => {\n        // Sort smallest to biggest (e.g., [120p, 480p, 1080p])\n        this.thumbnails.sort((x, y) => x.height - y.height);\n\n        this.player.debug.log('Preview thumbnails', this.thumbnails);\n\n        resolve();\n      };\n\n      // Via callback()\n      if (is.function(src)) {\n        src((thumbnails) => {\n          this.thumbnails = thumbnails;\n          sortAndResolve();\n        });","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/sampotts/plyr/blob/6520022413161d06e61d396810f48cac551fa7b5/src/js/plugins/preview-thumbnails.js#L123-L159","documentation":"Plyr's preview-thumbnails plugin needs at least one source of thumbnail data to do its job. getThumbnails() destructures config.previewThumbnails.src and, when is.empty(src) is true, throws before any network request is attempted. The throw occurs inside a Promise executor (new Promise((resolve) => {...})), so the Promise constructor converts it into a rejection of getThumbnails() — it surfaces as an unhandled rejection or via .catch() on the call site.","triggerScenarios":"The player is constructed with the previewThumbnails plugin loaded/enabled but config.previewThumbnails.src is omitted, null, undefined, an empty string, or an empty array. Also triggered when the whole previewThumbnails object is absent but the plugin is still instantiated, or when src is supplied under the wrong key (e.g. previewThumbnails.url).","commonSituations":"Enabling the plugin via enabled:true but forgetting the src field; copy-paste config from an older version where the key was named differently; conditional config that yields src: undefined in a specific build/environment; renaming the VTT endpoint variable and leaving src pointing at an empty value; disabling thumbnails by setting src to '' instead of setting enabled:false.","solutions":["Set previewThumbnails.src to a valid value: a VTT URL string, an array of VTT URLs (sorted by quality), or a function(thumbnails=>{...}) that returns thumbnail objects.","If you do not want thumbnails, remove the previewThumbnails key entirely or set previewThumbnails.enabled = false so the plugin never instantiates.","Double-check the key name is exactly 'src' and that it sits directly under previewThumbnails (not nested or renamed).","If generating thumbnails at runtime, pass src as a function so you control when thumbnails resolve."],"exampleFix":"// before\nconst player = new Plyr('#video', {\n  previewThumbnails: { enabled: true }\n});\n\n// after\nconst player = new Plyr('#video', {\n  previewThumbnails: { enabled: true, src: '/thumbnails.vtt' }\n});","handlingStrategy":"validation","validationCode":"// Run before constructing the Plyr player\nfunction validatePreviewThumbnails(config) {\n  const pt = config && config.previewThumbnails;\n  if (!pt || pt.enabled === false) return; // plugin disabled, nothing to check\n  const { src } = pt;\n  const ok =\n    typeof src === 'function' ||\n    (typeof src === 'string' && src.trim().length > 0) ||\n    (Array.isArray(src) && src.length > 0 && src.every(s => typeof s === 'string' && s.trim().length > 0));\n  if (!ok) {\n    throw new Error('previewThumbnails is enabled but src is missing/empty. Set previewThumbnails.src to a VTT url, an array of urls, or a function.');\n  }\n}\n\nvalidatePreviewThumbnails(playerConfig);\nconst player = new Plyr('#video', playerConfig);","typeGuard":"// Type guard narrowing to an accepted src shape\nfunction isValidPreviewThumbnailsSrc(src) {\n  return (\n    typeof src === 'function' ||\n    typeof src === 'string' ||\n    (Array.isArray(src) && src.every(s => typeof s === 'string'))\n  );\n}\n\nif (config.previewThumbnails && config.previewThumbnails.enabled !== false) {\n  if (!isValidPreviewThumbnailsSrc(config.previewThumbnails.src)) {\n    config.previewThumbnails.enabled = false;\n  }\n}","tryCatchPattern":"// getThumbnails()'s executor converts the throw into a rejection.\n// Chain .catch() wherever the plugin initializes (or wrap player.load) so a bad\n// config degrades gracefully instead of producing an unhandled rejection.\ntry {\n  // player setup that triggers thumbnail loading\n} catch (e) {\n  if (/Missing previewThumbnails.src/.test(e.message)) {\n    console.warn('Preview thumbnails disabled:', e.message);\n  } else {\n    throw e;\n  }\n}","preventionTips":["Keep a single config object for the player and lint it in a validate*() function before construction.","If thumbnails are optional, always pair enabled:false with an absent src rather than src:''.","When generating config dynamically, default src to undefined and only set enabled:true once src is known.","Add a unit test asserting getThumbnails rejects with the expected message when src is empty."],"tags":["config","preview-thumbnails","validation","plyr"],"backgroundTag":null,"analyzedSha":"6520022413161d06e61d396810f48cac551fa7b5","analyzedAt":"2026-08-13T09:56:39.192Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}