Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/faraasaaay/innertube-v1/llms.txt

Use this file to discover all available pages before exploring further.

The InnerTube SDK provides a complete set of methods for managing YouTube Music playlists: fetching content, creating new playlists, adding and removing tracks, reordering songs, and even uploading custom thumbnails. All write operations (create, rename, delete, add/remove/reorder songs, thumbnails) require a valid authenticated session set via YouTube.cookie.
All playlist management methods require the user to be signed in. Set YouTube.cookie to a valid YouTube Music session cookie string before calling any write operation. Read operations like YouTube.playlist() also include authenticated state (such as the editable flag) when a cookie is present.

Fetching a playlist

YouTube.playlist(playlistId) returns Result<PlaylistPage> containing the playlist metadata and the first page of tracks. The playlistId argument should be the bare playlist ID — without the VL prefix.

PlaylistPage fields

FieldTypeDescription
playlistPlaylistItemMetadata: id, title, author, thumbnail, editable flag, description
songsList<SongItem>First page of tracks; each SongItem carries a setVideoId required for remove/reorder
songsContinuationString?Continuation token embedded in the track list (preferred for paging)
continuationString?Fallback continuation token at the section list level
import com.metrolist.innertube.YouTube

suspend fun loadPlaylist(playlistId: String) {
    YouTube.playlist(playlistId).onSuccess { page ->
        val info = page.playlist
        println("Playlist: ${info.title} by ${info.author?.name}")
        println("Editable: ${info.isEditable}")
        println("Songs loaded: ${page.songs.size}")

        page.songs.forEach { song ->
            println("  ${song.title} — setVideoId=${song.setVideoId}")
        }
    }
}

Playlist continuation

For large playlists, pass the continuation token to YouTube.playlistContinuation() to fetch the next page of tracks. Repeat until continuation is null.
import com.metrolist.innertube.YouTube
import com.metrolist.innertube.models.SongItem

suspend fun fetchAllTracks(playlistId: String): List<SongItem> {
    val allSongs = mutableListOf<SongItem>()

    val firstPage = YouTube.playlist(playlistId).getOrThrow()
    allSongs += firstPage.songs

    var continuation = firstPage.songsContinuation ?: firstPage.continuation

    while (continuation != null) {
        val nextPage = YouTube.playlistContinuation(continuation).getOrThrow()
        if (nextPage.songs.isEmpty()) break
        allSongs += nextPage.songs
        continuation = nextPage.continuation
    }

    return allSongs
}

Creating a playlist

YouTube.createPlaylist(title) creates a new empty playlist and returns the new playlist ID as a String. This is a blocking call (backed by runBlocking internally) so call it from a coroutine or a background thread.
import com.metrolist.innertube.YouTube

fun createNewPlaylist(title: String): String {
    // Returns the new playlist ID (e.g. "PLxxxxxxxxxxxxxxxx")
    val playlistId = YouTube.createPlaylist(title)
    println("Created playlist: $playlistId")
    return playlistId
}

Renaming a playlist

import com.metrolist.innertube.YouTube

suspend fun renamePlaylist(playlistId: String, newName: String) {
    YouTube.renamePlaylist(playlistId, newName).onSuccess {
        println("Playlist renamed to \"$newName\"")
    }.onFailure { error ->
        println("Rename failed: ${error.message}")
    }
}

Deleting a playlist

import com.metrolist.innertube.YouTube

suspend fun deletePlaylist(playlistId: String) {
    YouTube.deletePlaylist(playlistId).onSuccess {
        println("Playlist $playlistId deleted")
    }
}

Adding songs

1

Add a single track

YouTube.addToPlaylist(playlistId, videoId) appends a single video to the end of a playlist.
import com.metrolist.innertube.YouTube

suspend fun addSong(playlistId: String, videoId: String) {
    YouTube.addToPlaylist(playlistId, videoId).onSuccess {
        println("Added $videoId to $playlistId")
    }
}
2

Add all tracks from another playlist

YouTube.addPlaylistToPlaylist(playlistId, addPlaylistId) appends every track from addPlaylistId into playlistId in one call.
import com.metrolist.innertube.YouTube

suspend fun addEntirePlaylist(targetId: String, sourceId: String) {
    YouTube.addPlaylistToPlaylist(targetId, sourceId).onSuccess {
        println("All tracks from $sourceId added to $targetId")
    }
}

Removing songs

YouTube.removeFromPlaylist(playlistId, videoId, setVideoId) removes a specific track occurrence from a playlist. The setVideoId value comes from SongItem.setVideoId — it is the unique positional identifier that distinguishes duplicate tracks within the same playlist.
import com.metrolist.innertube.YouTube
import com.metrolist.innertube.models.SongItem

suspend fun removeSong(playlistId: String, song: SongItem) {
    val setVideoId = song.setVideoId
        ?: error("setVideoId is null — fetch the playlist before removing a song")

    YouTube.removeFromPlaylist(
        playlistId = playlistId,
        videoId = song.id,
        setVideoId = setVideoId,
    ).onSuccess {
        println("Removed \"${song.title}\" from playlist")
    }
}
setVideoId is only populated when songs are fetched via YouTube.playlist() or YouTube.playlistContinuation(). It is null on SongItem objects returned from search or the library. Always load the playlist before removing or reordering its tracks.

Reordering songs

YouTube.moveSongPlaylist(playlistId, setVideoId, successorSetVideoId) moves a track to the position immediately before the track identified by successorSetVideoId. Pass successorSetVideoId = null to move the track to the end of the playlist.
import com.metrolist.innertube.YouTube

suspend fun moveSongToPosition(
    playlistId: String,
    songSetVideoId: String,
    beforeSetVideoId: String?,   // null = move to end
) {
    YouTube.moveSongPlaylist(
        playlistId = playlistId,
        setVideoId = songSetVideoId,
        successorSetVideoId = beforeSetVideoId,
    ).onSuccess {
        println("Song moved successfully")
    }
}

Custom thumbnails

1

Upload a custom thumbnail

YouTube.uploadCustomThumbnailLink(playlistId, imageBytes) is a two-step process handled automatically by the SDK:
  1. It requests an upload URL from the YouTube image upload service (using X-Goog-Upload-Command: start).
  2. It uploads the raw image bytes and receives an encrypted blob ID.
  3. It calls edit_playlist with a SetCustomThumbnailAction to associate the blob with the playlist.
The method returns Result<String?> where the success value is the new thumbnail URL.
import com.metrolist.innertube.YouTube
import java.io.File

suspend fun setPlaylistThumbnail(playlistId: String, imageFile: File) {
    val imageBytes = imageFile.readBytes()
    YouTube.uploadCustomThumbnailLink(playlistId, imageBytes).onSuccess { thumbnailUrl ->
        println("Thumbnail set: $thumbnailUrl")
    }.onFailure { error ->
        println("Upload failed: ${error.message}")
    }
}
2

Remove a custom thumbnail

YouTube.removeThumbnailPlaylist(playlistId) sends a RemoveCustomThumbnailAction to revert the playlist to its auto-generated thumbnail.
import com.metrolist.innertube.YouTube

suspend fun removeCustomThumbnail(playlistId: String) {
    YouTube.removeThumbnailPlaylist(playlistId).onSuccess {
        println("Custom thumbnail removed")
    }
}

VL prefix convention

Most YouTube methods that accept a playlistId expect the bare ID without the VL prefix (for example PLxxxxxxxxxx or RDxxxxxxxxxx). The VL prefix is stripped automatically with removePrefix("VL") inside InnerTube.addToPlaylist, removeFromPlaylist, renamePlaylist, and similar methods.However, when the SDK calls the /browse endpoint internally (for example in YouTube.playlist()), it prepends VL to construct the browse ID (VL$playlistId). You never need to add or remove this prefix yourself.

Complete playlist management example

import com.metrolist.innertube.YouTube

suspend fun playlistManagementDemo() {
    // 1. Create a new playlist
    val playlistId = YouTube.createPlaylist("My Workout Mix")
    println("Created: $playlistId")

    // 2. Add some songs
    listOf("dQw4w9WgXcQ", "L_jWHffIx5E", "hT_nvWreIhg").forEach { videoId ->
        YouTube.addToPlaylist(playlistId, videoId).getOrThrow()
    }

    // 3. Fetch the playlist to get setVideoIds
    val page = YouTube.playlist(playlistId).getOrThrow()
    println("Loaded ${page.songs.size} songs")

    // 4. Move the second song to the end
    val secondSong = page.songs.getOrNull(1)
    if (secondSong?.setVideoId != null) {
        YouTube.moveSongPlaylist(
            playlistId = playlistId,
            setVideoId = secondSong.setVideoId!!,
            successorSetVideoId = null,  // move to end
        ).getOrThrow()
    }

    // 5. Remove the first song
    val firstSong = page.songs.firstOrNull()
    if (firstSong?.setVideoId != null) {
        YouTube.removeFromPlaylist(
            playlistId = playlistId,
            videoId = firstSong.id,
            setVideoId = firstSong.setVideoId!!,
        ).getOrThrow()
    }

    // 6. Rename the playlist
    YouTube.renamePlaylist(playlistId, "My Updated Mix").getOrThrow()

    // 7. Delete when done
    YouTube.deletePlaylist(playlistId).getOrThrow()
    println("Playlist deleted")
}

Build docs developers (and LLMs) love