All guides

October 2, 2026 · 5 min read

How to Get YouTube Transcripts in JavaScript and Node.js

YouTube does not offer a public endpoint for reading another channel's captions, and scraping the watch page from a server tends to get blocked. This guide shows how to get transcripts in JavaScript and TypeScript with plain fetch and the TranscriptYT API, with no SDK to install.

Fetch a transcript

Create a free key in the dashboard (100 transcripts, no card) and keep it in an environment variable on your server. fetch is built into Node.js 18+, Bun, Deno, and every edge runtime.

type Segment = { start: number; duration: number; text: string };
type Transcript = {
  videoId: string;
  title: string | null;
  language: string;
  translated: boolean;
  source: "captions" | "speech_to_text";
  segments: Segment[];
  text: string;
};

const res = await fetch(
  "https://transcript-yt.com/v1/transcript?url=" + encodeURIComponent("https://youtu.be/dQw4w9WgXcQ"),
  { headers: { Authorization: "Bearer " + process.env.TRANSCRIPTYT_KEY } },
);
if (!res.ok) throw new Error((await res.json()).error?.message);
const transcript: Transcript = await res.json();

Use it from a Next.js route handler

Call the API from server code so your key never reaches the browser. A route handler can pass the result straight through to your frontend.

// app/api/transcript/route.ts
export async function GET(request: Request) {
  const video = new URL(request.url).searchParams.get("v") ?? "";
  const res = await fetch(
    "https://transcript-yt.com/v1/transcript?url=" + encodeURIComponent(video),
    { headers: { Authorization: "Bearer " + process.env.TRANSCRIPTYT_KEY } },
  );
  return new Response(await res.text(), {
    status: res.status,
    headers: { "Content-Type": res.headers.get("Content-Type") ?? "application/json" },
  });
}

Download subtitles or plain text

Add format=srt, vtt, text, csv, or md to get the file body directly. Add timestamps=true to prefix text and Markdown lines with [HH:MM:SS], or paragraphs=true to merge caption cues into readable paragraphs, which works well as LLM context.

import { writeFile } from "node:fs/promises";

const srt = await fetch(
  "https://transcript-yt.com/v1/transcript?url=dQw4w9WgXcQ&format=srt",
  { headers: { Authorization: "Bearer " + process.env.TRANSCRIPTYT_KEY } },
).then((r) => r.text());
await writeFile("captions.srt", srt);

Translate and pick languages

Pass language to select a caption track, or translate_to to machine-translate into any of 150+ languages with segment timings preserved. GET /v1/languages lists a video's tracks for free.

Handle errors

Every error has the shape { error: { code, message } }. Retry BLOCKED, RATE_LIMITED, and UPSTREAM_ERROR with backoff; treat VIDEO_UNAVAILABLE, NO_CAPTIONS, and LANGUAGE_NOT_AVAILABLE as final. A 402 means the account is out of credits. Failed calls are never billed, and the X-Credits-Remaining header tells you your balance after every request.

Try the TranscriptYT API

Get transcripts as JSON, text, SRT, or VTT. 100 free credits, no card required.

More guides