Skip to content

Instantly share code, notes, and snippets.

@Axenide
Created August 2, 2024 07:05
Show Gist options
  • Save Axenide/cd973f5e8edd8640f1c9a77d7b78b510 to your computer and use it in GitHub Desktop.
Save Axenide/cd973f5e8edd8640f1c9a77d7b78b510 to your computer and use it in GitHub Desktop.
Bulk download your Suno songs and videos!

How to bulk download your Suno library

Today my dad asked me for a way to download all of his Suno songs. (He really loves writing songs, and now he can give them life easily. What a time to be alive!). Luckily, I found this gist and it works great, but my dad also wanted to get the videos generated, so I modified it a little.

copy(
    [...$('[role="grid"]')[Object.keys($('[role="grid"]')).filter(x => x.startsWith('__reactProps'))[0]].children[0].props.values[0][1].collection]
    .reduce((acc, x) => {
        if (x.value.audio_url) acc.push(x.value.audio_url);
        if (x.value.video_url) acc.push(x.value.video_url);
        return acc;
    }, [])
    .join('\n')
)

You just open your browser console and paste it.

What this does is copying the links of all the songs currently displayed in your library, you can save them wherever you want and use your download manager of choice. Personally I used wget, it is this simple:

wget -i urls.txt

If you want to just get your urls.txt, this version saves it automatically for you (but you will have your links in different files for each page of your library):

let links = 
    [...$('[role="grid"]')[Object.keys($('[role="grid"]')).filter(x => x.startsWith('__reactProps'))[0]].children[0].props.values[0][1].collection]
    .reduce((acc, x) => {
        if (x.value.audio_url) acc.push(x.value.audio_url);
        if (x.value.video_url) acc.push(x.value.video_url);
        return acc;
    }, [])
    .join('\n');

let blob = new Blob([links], { type: 'text/plain' });

let a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'urls.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
@amoyssiadis
Copy link

Thanks! I tried modifying it to retrieve the WAV links, but I haven't been successful. Do you have any suggestions?

@Axenide
Copy link
Author

Axenide commented Nov 25, 2024

Thanks! I tried modifying it to retrieve the WAV links, but I haven't been successful. Do you have any suggestions?

@amoyssiadis I'm not able to test that since I don't have a subscription and WAV files aren't available in the free plan. Sorry.

@DocPeter
Copy link

I couldn't get the scripts to work - security blocks etc. etc.
However, I discovered this utility which enables download of any Suno playlist to a folder of your choosing. Works brilliantly (and well worth a donation of a cup of coffee).
https://drummersi.github.io/suno-downloader/

@killmark-prog
Copy link

killmark-prog commented Mar 14, 2025

Thanks! I tried modifying it to retrieve the WAV links, but I haven't been successful. Do you have any suggestions?

WAV files are only generated when you click the the option to download the WAV file, whereas MP3 and MP4 files already exist. So whilst you can get the link, it won't work until the wav file window pops up with its generating message.

Here is another version, which you can use to assign "unique" filenames for the download aspect. This will work on playlists as well as the library which enables many more tracks to be downloaded


 javascript:(function() {
  try {
    const songRows = document.querySelectorAll('[data-testid="song-row"]');

    if (!songRows.length) {
      throw new Error("No song rows found.");
    }

    const downloadLinks = [];
    const titleCounts = {}; // Keep track of how many times each title appears.

    songRows.forEach(row => {
      const clipId = row.getAttribute('data-clip-id');
      const titleElement = row.querySelector('.font-sans.text-base.font-medium.line-clamp-1.break-all.text-foreground-primary');

      if (clipId && titleElement) {
        let songTitle = titleElement.textContent.trim();
        songTitle = songTitle.replace(/[\\/:*?"<>|]/g, '');

        // Check if the title has been used before.
        if (titleCounts[songTitle]) {
          titleCounts[songTitle]++;
          songTitle += `_${String(titleCounts[songTitle] - 1).padStart(2, '0')}`; // Add _01, _02, etc.
        } else {
          titleCounts[songTitle] = 1; // Initialize the count for this title.
        }

        const mp3Url = `https://cdn1.suno.ai/${clipId}.mp3`;
        const mp4Url = `https://cdn1.suno.ai/${clipId}.mp4`;

        downloadLinks.push(`${songTitle}.mp3|${mp3Url}`);
        downloadLinks.push(`${songTitle}.mp4|${mp4Url}`);
      } else {
        console.warn("Song row found without a clip ID or title element:", row);
      }
    });

    if (!downloadLinks.length) {
      throw new Error("No download links could be constructed.");
    }

    copy(downloadLinks.join('\n'));
    alert(`Copied ${downloadLinks.length} download links to clipboard (with unique filenames).`);

  } catch (error) {
    console.error("Suno Download Link Scraper Error:", error);
    alert("Error: " + error.message);
  }
})()

@edzme
Copy link

edzme commented Jun 4, 2025

Here's a quick bash script. After running @killmark-prog 's js... save that to a file like piano.txt. Save the script below to downloader.sh

Filters out the mp4 and downloads all the mp3s, makes a new folder based on the txt filename.

Run it like ./downloader.sh --songfile piano.txt

#!/bin/bash

set -e

# Parse arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        --songfile)
            SONGFILE="$2"
            shift 2
            ;;
        *)
            echo "Unknown parameter passed: $1"
            echo "Usage: ./downloader.sh --songfile <filename>"
            exit 1
            ;;
    esac
done

# Ensure SONGFILE is provided
if [[ -z "$SONGFILE" ]]; then
    echo "Error: --songfile parameter is required."
    echo "Usage: ./downloader.sh --songfile <filename>"
    exit 1
fi

# Check if file exists
if [[ ! -f "$SONGFILE" ]]; then
    echo "Error: File '$SONGFILE' does not exist."
    exit 1
fi

# Derive folder name from songfile (e.g., songs.txt -> songs_files)
BASENAME=$(basename "$SONGFILE" .txt)
DEST_DIR="${BASENAME}_files"

# Create destination folder if it doesn't exist
mkdir -p "$DEST_DIR"

# Read lines and download only .mp3 files
while IFS='|' read -r FILENAME URL; do
    if [[ "$FILENAME" == *.mp3 ]]; then
        echo "Downloading $FILENAME..."
        curl -sSL "$URL" -o "${DEST_DIR}/${FILENAME}"
    fi
done < "$SONGFILE"

echo "✅ All mp3 files downloaded to: $DEST_DIR"

@sleepingcat4
Copy link

@edzme do you have working code to download sudo audio links? I tried pasting the code on console but it doesn't seem to work.

@edzme
Copy link

edzme commented Jun 9, 2025

@sleepingcat4

  • save that text block as a file named downloader.sh
  • then save the output from the clipboard from @killmark-prog 's code to a textfile called piano.txt in the same directory
  • then run in terminal (in that directory) ./downloader.sh --songfile piano.txt

note probably wont work on a windows machine.. if thats the case throw it all into chatgpt and have it convert it to windows compatible

@sleepingcat4
Copy link

@edzme thanks! btw is there a way to map entire suno website?

@sleepingcat4
Copy link

@killmark-prog did you try scraping this page? It seems to be strictly guarded by Cloudfare

https://suno.com/search?type=public_song

@sleepingcat4
Copy link

if anyone searching for getting list of playlists, can use this following code

javascript:(function() {
    try {
      const playlistLinks = new Set();
      const anchors = document.querySelectorAll('a[href^="/playlist/"]');
  
      anchors.forEach(a => {
        const href = a.getAttribute('href');
        if (href && /^\/playlist\/[0-9a-f\-]{36}$/.test(href)) {
          playlistLinks.add(`https://suno.com${href}`);
        }
      });
  
      if (!playlistLinks.size) throw new Error("No playlist links found.");
      copy([...playlistLinks].join('\n'));
      alert(`Copied ${playlistLinks.size} playlist link(s) to clipboard.`);
  
    } catch (error) {
      console.error("Suno Playlist Scraper Error:", error);
      alert("Error: " + error.message);
    }
  })()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment