DEV Community

krlz
krlz

Posted on

Automate Spotify to YouTube Music Playlist Transfer with TypeScript

Are you tired of manually recreating your Spotify playlists on YouTube Music? In this guide, I'll show you how to build an automated solution using TypeScript that transfers your playlists seamlessly between these two platforms.

Why Automate Playlist Transfers?

Switching between music streaming services shouldn't mean losing your carefully curated playlists. Whether you're migrating permanently or maintaining playlists across both platforms, automation saves hours of tedious manual work.

Prerequisites

  • Node.js 18+ installed
  • A Spotify Developer account
  • Basic TypeScript knowledge
  • A YouTube Music account

Step 1: Setting Up the Project

First, create a new TypeScript project:

mkdir spotify-to-ytmusic
cd spotify-to-ytmusic
npm init -y
npm install typescript ts-node @types/node --save-dev
npm install @spotify/web-api-ts-sdk ytmusic-api
npx tsc --init
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure Spotify API Access

Go to the Spotify Developer Dashboard and create an app. You'll need:

  • Client ID
  • Client Secret
  • Redirect URI (set to http://localhost:3000/callback)

Create a .env file:

SPOTIFY_CLIENT_ID=your_client_id
SPOTIFY_CLIENT_SECRET=your_client_secret
SPOTIFY_REDIRECT_URI=http://localhost:3000/callback
Enter fullscreen mode Exit fullscreen mode

Step 3: Fetch Spotify Playlist Tracks

Using the official Spotify TypeScript SDK:

import { SpotifyApi } from "@spotify/web-api-ts-sdk";

interface Track {
  name: string;
  artist: string;
  album: string;
}

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;
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Search and Match on YouTube Music

Using the ytmusic-api package to find matching songs:

import YTMusic from "ytmusic-api";

interface YTMusicTrack {
  videoId: string;
  name: string;
  artist: string;
}

async function searchYouTubeMusic(
  ytmusic: YTMusic,
  track: Track
): 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}`);
  }

  return null;
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Create the Transfer Function

Now let's combine everything into a complete transfer function:

import YTMusic from "ytmusic-api";
import { SpotifyApi } from "@spotify/web-api-ts-sdk";

async function transferPlaylist(
  spotifyPlaylistId: string,
  newPlaylistName: string
): Promise<void> {
  console.log("🎡 Starting playlist transfer...");

  // Initialize YouTube Music API
  const ytmusic = new YTMusic();
  await ytmusic.initialize();

  // Fetch Spotify tracks
  console.log("πŸ“₯ Fetching Spotify playlist tracks...");
  const spotifyTracks = await getSpotifyPlaylistTracks(spotifyPlaylistId);
  console.log(`Found ${spotifyTracks.length} tracks`);

  // Search and match 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}`);
    }

    // Rate limiting to avoid API throttling
    await new Promise((r) => setTimeout(r, 500));
  }

  // Summary
  console.log("\nπŸ“Š Transfer Summary:");
  console.log(`βœ“ Matched: ${matchedTracks.length} tracks`);
  console.log(`βœ— Not found: ${notFound.length} tracks`);

  if (notFound.length > 0) {
    console.log("\nTracks not found:");
    notFound.forEach((t) => console.log(`  - ${t}`));
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Adding Tracks to YouTube Music Playlist

For authenticated playlist creation, you'll need to use youtube-music-ts-api with cookie authentication:

import YouTubeMusic from "youtube-music-ts-api";

async function createYTMusicPlaylist(
  cookieString: string,
  playlistName: string,
  videoIds: string[]
): Promise<void> {
  const ytm = new YouTubeMusic();
  const ytma = await ytm.authenticate(cookieString);

  // Create new playlist
  const playlist = await ytma.createPlaylist(
    playlistName,
    "Imported from Spotify",
    "PRIVATE"
  );

  console.log(`Created playlist: ${playlist.name}`);

  // Note: Adding tracks requires track objects
  // You'll need to fetch full track details first
  // using ytma.getTrack() for each videoId
}
Enter fullscreen mode Exit fullscreen mode

Getting YouTube Music Cookies

To authenticate with YouTube Music:

  1. Open YouTube Music in your browser
  2. Open Developer Tools (F12)
  3. Go to Network tab
  4. Perform any action (like playing a song)
  5. Find a request to music.youtube.com
  6. Copy the Cookie header value

Complete Working Example

Here's the full index.ts:

import "dotenv/config";
import YTMusic from "ytmusic-api";
import { SpotifyApi } from "@spotify/web-api-ts-sdk";

interface Track {
  name: string;
  artist: string;
}

async function main() {
  // Spotify playlist ID (from URL)
  const playlistId = "37i9dQZF1DXcBWIGoYBM5M"; // Today's Top Hits

  const sdk = SpotifyApi.withClientCredentials(
    process.env.SPOTIFY_CLIENT_ID!,
    process.env.SPOTIFY_CLIENT_SECRET!
  );

  // Get playlist info
  const playlist = await sdk.playlists.getPlaylist(playlistId);
  console.log(`Playlist: ${playlist.name}`);

  // Get tracks
  const response = await sdk.playlists.getPlaylistItems(playlistId);
  const tracks: Track[] = response.items
    .filter((item) => item.track && "name" in item.track)
    .map((item) => ({
      name: item.track!.name,
      artist: (item.track as any).artists[0]?.name || "Unknown",
    }));

  console.log(`Found ${tracks.length} tracks`);

  // Initialize YT Music
  const ytmusic = new YTMusic();
  await ytmusic.initialize();

  // Search for each track
  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`);
    }

    await new Promise((r) => setTimeout(r, 300));
  }
}

main().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Alternative: Using Existing Tools

If you prefer ready-made solutions:

Best Practices

  1. Rate Limiting: Add delays between API calls to avoid throttling
  2. Error Handling: Some tracks may not have exact matches
  3. Fuzzy Matching: Consider implementing fuzzy string matching for better results
  4. Batch Processing: For large playlists, process in chunks
  5. Logging: Keep track of failed matches for manual addition

Conclusion

With this TypeScript solution, you can automate the tedious process of transferring playlists between Spotify and YouTube Music. The code is extensible - you can add features like:

  • Sync scheduling with cron jobs
  • Two-way synchronization
  • Web UI with Express.js
  • Discord bot integration

The full source code is available on GitHub. Happy coding!


Resources:

Top comments (0)