Automate Spotify to YouTube Music Playlist Transfer with TypeScript
Source: Dev.to
📋 Overview
Switching between music‑streaming services shouldn’t mean losing your carefully curated playlists. Whether you’re migrating permanently or maintaining playlists on both platforms, a small TypeScript script can do the heavy lifting for you.
🛠 Prerequisites
| Requirement | Details |
|---|---|
| Node.js | v18 or newer |
| Spotify Developer | Account with an app created (client ID, client secret, redirect URI) |
| YouTube Music | A regular account (you’ll need its authentication cookie) |
| TypeScript | Basic knowledge of the language |
| Environment variables | .env file for secrets |
🚀 Project Setup
# Create a new folder and initialise the project
mkdir spotify-to-ytmusic
cd spotify-to-ytmusic
npm init -y
# Development dependencies
npm install typescript ts-node @types/node --save-dev
# Runtime dependencies
npm install @spotify/web-api-ts-sdk ytmusic-api youtube-music-ts-api dotenv
# Initialise a tsconfig.json
npx tsc --init
.env
Create a file called .env in the project root and paste the following (replace the placeholders with your own values):
SPOTIFY_CLIENT_ID=your_client_id
SPOTIFY_CLIENT_SECRET=your_client_secret
SPOTIFY_REDIRECT_URI=http://localhost:3000/callback
🎧 Spotify – Fetch Playlist Tracks
// src/spotify.ts
import { SpotifyApi } from "@spotify/web-api-ts-sdk";
export interface Track {
name: string;
artist: string;
album: string;
}
/**
* Retrieves all tracks from a Spotify playlist.
*/
export async function getSpotifyPlaylistTracks(
playlistId: string
): Promise<Track[]> {
const sdk = SpotifyApi.withClientCredentials(
process.env.SPOTIFY_CLIENT_ID!,
process.env.SPOTIFY_CLIENT_SECRET!
);
const tracks: Track[] = [];
let offset = 0;
const limit = 50;
while (true) {
const response = await sdk.playlists.getPlaylistItems(
playlistId,
undefined,
undefined,
limit,
offset
);
for (const item of response.items) {
if (item.track && "name" in item.track) {
tracks.push({
name: item.track.name,
artist: item.track.artists[0]?.name ?? "Unknown",
album: item.track.album?.name ?? "Unknown",
});
}
}
if (response.items.length < limit) {
break;
}
offset += limit;
}
return tracks;
}
🔎 Search YouTube Music
// src/ytmusic.ts
import YTMusic from "ytmusic-api";
export interface YTMusicTrack {
videoId: string;
name: string;
artist: string;
}
/**
* Searches YouTube Music for a track and returns the first match.
*/
export async function searchYouTubeMusic(
ytmusic: YTMusic,
track: { name: string; artist: string }
): Promise<YTMusicTrack | null> {
const query = `${track.name} ${track.artist}`;
try {
const results = await ytmusic.searchSongs(query);
if (results.length > 0) {
const match = results[0];
return {
videoId: match.videoId,
name: match.name,
artist: match.artist?.name ?? "Unknown",
};
}
} catch (error) {
console.error(`Failed to find: ${query}`, error);
}
return null;
}
🔄 Transfer Function – Glue Everything Together
// src/transfer.ts
import YTMusic from "ytmusic-api";
import { getSpotifyPlaylistTracks, Track } from "./spotify";
import { searchYouTubeMusic, YTMusicTrack } from "./ytmusic";
export async function transferPlaylist(
spotifyPlaylistId: string,
newPlaylistName: string
): Promise<void> {
console.log("🎵 Starting playlist transfer...");
// Initialise YouTube Music API
const ytmusic = new YTMusic();
await ytmusic.initialize();
// 1️⃣ Fetch Spotify tracks
console.log("📥 Fetching Spotify playlist tracks...");
const spotifyTracks = await getSpotifyPlaylistTracks(spotifyPlaylistId);
console.log(`Found ${spotifyTracks.length} tracks`);
// 2️⃣ Search for each track on YouTube Music
console.log("🔍 Searching for matches on YouTube Music...");
const matchedTracks: YTMusicTrack[] = [];
const notFound: string[] = [];
for (const track of spotifyTracks) {
const match = await searchYouTubeMusic(ytmusic, track);
if (match) {
matchedTracks.push(match);
console.log(`✓ Found: ${track.name} – ${track.artist}`);
} else {
notFound.push(`${track.name} – ${track.artist}`);
console.log(`✗ Not found: ${track.name} – ${track.artist}`);
}
// Simple rate‑limiting to avoid throttling
await new Promise((r) => setTimeout(r, 500));
}
// 3️⃣ Summary
console.log("\n📊 Transfer Summary:");
console.log(`✓ Matched: ${matchedTracks.length}`);
console.log(`✗ Not found: ${notFound.length}`);
if (notFound.length) {
console.log("\nTracks not found:");
notFound.forEach((t) => console.log(` - ${t}`));
}
// 4️⃣ (Optional) Create a YouTube Music playlist and add the matches
// await createYTMusicPlaylist(...);
}
📂 Create a YouTube Music Playlist (Cookie‑Based Auth)
// src/ytmusic-create.ts
import YouTubeMusic from "youtube-music-ts-api";
export async function createYTMusicPlaylist(
cookieString: string,
playlistName: string,
videoIds: string[]
): Promise<void> {
const ytm = new YouTubeMusic();
const ytma = await ytm.authenticate(cookieString);
// Create the new playlist
const playlist = await ytma.createPlaylist(
playlistName,
"Imported from Spotify",
"PRIVATE"
);
console.log(`Created playlist: ${playlist.name}`);
// Add tracks – you need full track objects, not just IDs.
// Example (pseudo‑code):
// for (const id of videoIds) {
// const track = await ytma.getTrack(id);
// await ytma.addTrackToPlaylist(playlist.id, track);
// }
}
How to obtain the authentication cookie
- Open YouTube Music in your browser.
- Press F12 → Network tab.
- Perform any action (e.g., play a song).
- Locate a request to
music.youtube.comand copy the Cookie header value. - Paste that string into your script (or store it in an environment variable).
📂 Full index.ts Example
// src/index.ts
import "dotenv/config";
import { transferPlaylist } from "./transfer";
async function main() {
// Spotify playlist ID (taken from the URL)
const spotifyPlaylistId = "37i9dQZF1DXcBWIGoYBM5M"; // Today's Top Hits
// Desired name for the new YouTube Music playlist
const ytPlaylistName = "Today's Top Hits (from Spotify)";
await transferPlaylist(spotifyPlaylistId, ytPlaylistName);
}
main().catch((err) => {
console.error("❌ Unexpected error:", err);
process.exit(1);
});
Note: The snippet above only performs the search & matching step.
To actually create the YouTube Music playlist and add the tracks, callcreateYTMusicPlaylist(see the previous section) after the matching phase, passing the cookie string and the array ofvideoIds frommatchedTracks.
📦 Running the Project
# Compile & run with ts-node (development)
npx ts-node src/index.ts
# Or compile first, then run the JavaScript output
npm run build # assuming you added a "build" script that runs tsc
node dist/index.js
🧩 What’s Next?
- Error handling – retry failed searches, fallback to fuzzy matching.
- Batching – add tracks to the new playlist in bulk to speed things up.
- OAuth for Spotify – use the Authorization Code flow if you need to read private playlists or write to a user’s library.
- CLI wrapper – expose the script as a simple command‑line tool (
npx spotify-to-ytmusic …).
🎉 Happy coding!
You now have a fully‑functional, TypeScript‑based pipeline that can copy any Spotify playlist to YouTube Music with just a few commands. Feel free to tweak the code, add more logging, or integrate it into a larger automation suite.
import { YTMusic } from "ytmusic-api";
import SpotifyWebApi from "spotify-web-api-node";
async function main() {
// -------------------------------------------------
// 1️⃣ Get Spotify playlist tracks
// -------------------------------------------------
const spotify = new SpotifyWebApi({
clientId: process.env.SPOTIFY_CLIENT_ID!,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET!,
redirectUri: "http://localhost:8888/callback",
});
// Get an access token (you can also use a refresh token)
const { body: tokenData } = await spotify.clientCredentialsGrant();
spotify.setAccessToken(tokenData.access_token);
// Replace with your playlist ID
const playlistId = "YOUR_SPOTIFY_PLAYLIST_ID";
const data = await spotify.getPlaylistTracks(playlistId, {
fields: "items(track(name,artists(name)))",
limit: 100,
});
const tracks = data.body.items.map((item) => ({
name: item.track!.name,
artist: (item.track as any).artists[0]?.name || "Unknown",
}));
console.log(`Found ${tracks.length} tracks`);
// -------------------------------------------------
// 2️⃣ Initialise YouTube Music client
// -------------------------------------------------
const ytmusic = new YTMusic();
await ytmusic.initialize();
// -------------------------------------------------
// 3️⃣ Search & match each track (first 5 as demo)
// -------------------------------------------------
for (const track of tracks.slice(0, 5)) {
const query = `${track.name} ${track.artist}`;
const results = await ytmusic.searchSongs(query);
if (results.length > 0) {
console.log(`✓ ${track.name} → ${results[0].name}`);
} else {
console.log(`✗ ${track.name} – Not found`);
}
// Respect rate limits
await new Promise((r) => setTimeout(r, 300));
}
}
main().catch(console.error);
📦 Ready‑made solutions
- spotify-to-youtube – NPM package for matching tracks
- SpotTransfer – Full migration tool (GUI)
- spotify_to_ytmusic – Python CLI tool
🛠️ Tips for a robust implementation
- Rate limiting – Add delays (e.g.,
300 ms) between API calls to avoid throttling. - Error handling – Some tracks may not have exact matches; log these for manual review.
- Fuzzy matching – Use a fuzzy‑string library (e.g.,
fuzzball) to improve match accuracy. - Batch processing – For large playlists, process in chunks and persist intermediate results.
- Logging – Keep a CSV/JSON of failed matches for later manual addition.
🚀 Extending the script
- Sync scheduling – Run the script on a cron job for periodic updates.
- Two‑way sync – Detect additions/removals on either platform and keep them in sync.
- Web UI – Wrap the logic in an Express.js app for a simple front‑end.
- Discord bot – Expose the functionality via slash commands.
The full source code (including the above script) is available on GitHub. Happy coding!
📚 Resources
- Spotify Web API TypeScript SDK –
- ytmusic-api –
- youtube-music-ts-api –
- Spotify Developer Documentation –