If yt-dlp prints No supported JavaScript runtime could be found, suddenly lists far fewer formats, or your distro's package broke on YouTube overnight, the fix is usually one install: Deno 2.3 or newer. yt-dlp now hands YouTube's player challenges to a real JavaScript runtime, and Deno is the only one it uses without being told to. If you already have Node 22 installed, yt-dlp ignores it unless you pass a flag.

The short version, if you installed the official binary:

deno --version # needs 2.3.0 or newer yt-dlp -U 

pip, pipx and distro installs need one more step, covered below. After that I'll get to why I went down this hole at all: archiving my YouTube playlists before more of them turn into "video unavailable".

Why yt-dlp stopped working on YouTube

With yt-dlp 2025.11.12 (released November 12, 2025), the maintainers added support for external JavaScript runtimes and declared YouTube extraction without one deprecated. YouTube's player code had gotten too complex for yt-dlp's built-in Python JS interpreter, so the challenge solving moved to a set of scripts called EJS, packaged separately as yt-dlp-ejs, which run in a real JS engine.

Without a runtime you'll see warnings like:

No supported JavaScript runtime could be found. YouTube extraction without a JS runtime has been deprecated, and some formats may be missing. 

People asked for that warning to be removed in the announcement thread. Maintainer bashonly said no: "The user needs to be informed." There is an extractor-arg that hides it, and bashonly told people not to use it and to install a runtime instead. I agree with that. I'd much rather get a noisy warning than a downloader that quietly hands me 360p and lets me think everything's fine.

That leaves two separate things you need:

  1. A supported JS runtime on your PATH.
  2. The yt-dlp-ejs scripts, at the exact version your yt-dlp release expects.

Most broken setups have one of the two and not the other.

Deno 2.0 isn't enough anymore: check your runtime version

This catches people who installed a runtime last year and assume they're covered. The original announcement listed Deno 2.0.0 and Node 20.0.0 as minimums. The current EJS wiki page (last edited July 12, 2026) is stricter:

Runtime Minimum Enabled by default? How to enable
Deno 2.3.0 Yes nothing, or --js-runtimes deno:/path/to/deno
Node 22.0.0 No --js-runtimes node
QuickJS / QuickJS-NG QuickJS 2023-12-9, any QuickJS-NG No --js-runtimes quickjs
Bun 1.2.11 (max 1.3.14) No --js-runtimes bun, deprecated

Only Deno is on by default. The others are off for security reasons. Deno runs the scripts with restricted permissions, Node restricts some, and Bun restricts none. If you've got Node 22 already installed and yt-dlp still complains, that's why: it won't touch Node until you pass the flag.

Two more gotchas from the wiki:

  • If you grab Deno from its GitHub releases, download deno, not denort.
  • QuickJS before 2025-4-26 and QuickJS-NG before 0.12.0 lack optimizations and can take several minutes to run. If you picked QuickJS because it's small and now every download hangs, that's expected behaviour on an old build.

Bun's position is shaky. Versions after 1.3.14 aren't supported and the wiki says support "may be dropped entirely in the future". I wouldn't build anything on it.

Fixing yt-dlp for each install method: binary, pip, distro

Check what you have first:

yt-dlp --version deno --version 

If Deno is missing or older than 2.3, install it from deno.com or your package manager. Then fix the EJS side based on how yt-dlp got onto your machine.

If you run the official binary (the PyInstaller executables or the zipimport Unix binary from GitHub releases), yt-dlp-ejs is already bundled. Update and you're done:

yt-dlp -U 

With pip or pipx, the plain yt-dlp package doesn't pull in EJS. You need the default extra:

pip install -U "yt-dlp[default]" 

You can install yt-dlp-ejs on its own, but its version has to match the one pinned in yt-dlp's pyproject.toml. yt-dlp pins each release to one EJS version and ignores anything else, so a stale yt-dlp-ejs acts like no yt-dlp-ejs.

Distro packages, Homebrew, pacman and friends are where most of the confusion comes from. Your packager may or may not ship yt-dlp-ejs, and if they do it may lag behind. The wiki's answer is to let yt-dlp fetch the scripts itself:

# Deno or Bun: pull EJS from npm yt-dlp --remote-components ejs:npm URL # any runtime: pull EJS from GitHub yt-dlp --remote-components ejs:github URL 

ejs:npm only works with Deno and Bun. ejs:github can fail if GitHub is unreachable, including IPv6-only setups like --force-ipv6.

My opinion: if you're on a distro package and you care about YouTube, stop fighting it and use the official binary or pipx. The maintainers suggested in the announcement that packagers make deno, nodejs, quickjs and bun optional dependencies. Whether your distro did is outside your control, and you'll be back here every time YouTube changes something.

Make the fix permanent in your yt-dlp config

The wiki recommends putting these in your config so every run (and every wrapper that shells out to yt-dlp) gets them. In ~/.config/yt-dlp/config:

--js-runtimes deno --remote-components ejs:npm 

Swap in --js-runtimes node or --js-runtimes quickjs:/path/to/qjs if that's what you run.

Now archive your YouTube playlists before more videos vanish

The post that hit the HN front page (riffing on Laura Michet's piece) makes the case bluntly: "the things you love do not belong to you unless you claim them for yourself." If you want to see how much you've already lost, open your liked-videos playlist and turn on unavailable videos in the playlist options. The early years tend to be a graveyard.

The author's setup is worth copying:

  • ytdl-sub drives yt-dlp from YAML. It was the only wrapper they found that kept playlist structure intact. The Docker Compose version comes with a VSCode-based web editor.
  • Jellyfin plays it back on the TV, using a library in TV Shows mode called "YouTube" with all metadata scrapers turned off and file data preferred over file names.
  • Tdarr re-encodes to HEVC/H.265. It saved them about 700GB on a 2TB library, but they warn it "has a tendency to break on anything remotely complex" and don't really recommend it, though in a pinch it does the job.

A minimal ytdl-sub subscription file looks like this. The README uses Plex presets and says you can swap in Jellyfin:

__preset__: overrides: tv_show_directory: "/tv_shows" Jellyfin TV Show by Date: = Documentaries: "Cosmos - What If": "https://www.youtube.com/playlist?list=PLZdXRHYAVxTJno6oFF9nLGuwXNGYHmE8U" 

Run it with ytdl-sub sub subscriptions.yaml.

One trap the author hit: by default ytdl-sub sets the Season field by year but puts files in folders named after the playlist, so Jellyfin shows every video twice. Their fix is to set the playlist title as the Season Number in the ytdl-sub config. In their words, "it seems like it should break, but it doesn't." Make that change before your first big import. Changing it afterwards means a full library reimport, which they were avoiding with roughly a terabyte of video.

And since ytdl-sub calls yt-dlp, everything above applies inside the container too. Exec in, run deno --version, and if it isn't there, add it or point --js-runtimes at whatever runtime the image ships.

What the HN thread fought about

The discussion went where you'd expect. One commenter estimated about 10% of a five-year-old playlist was unavailable, and several pointed out that YouTube doesn't even keep the title of a removed video, so you can't tell what you lost. Others said storage is a smaller problem than people assume: one worked out that 720p at 1.5 Mbps, four hours a day, comes to less than a terabyte a year.

The legal argument never settled. account42 put it shortest: "That's the thing though, it was never yours." My position: keep copies of what you have the right to keep, like your own uploads, Creative Commons work, and creators who explicitly say go ahead, and don't redistribute what you don't. A private copy of a Let's Play you fall asleep to is a different thing from seeding a channel's back catalogue.

Thread alternatives if ytdl-sub's YAML isn't for you: Tube Archivist, Pinchflat and Youtarr all came up.

Go look at your oldest liked-videos playlist with unavailable videos shown. How many are already gone? And did yt-dlp break for you after the runtime change, or was it fine the whole time?

Sources