Help Wizard

Step 1



Please see below the most popular frequently asked questions.

Loading article...

Loading faqs...


Ongoing Issues

Please see below the current ongoing issues which are under investigation.

Loading issue...

Loading ongoing issues...


[Release] Unportify v1.4.3


[Release] Unportify v1.4.3

NOTE: If you don't care about my story, jump to the bold text below and ignore the rest.


After using Google Play Music for a year, I started to miss some of Spotify's details.

On Google Music, you can be listening to calm, relaxing music, and get jump scared when the next song starts playing because the volume isn't equal for every song.

I also missed Spotify's humanized shuffle, where the shuffle feels random even though it isn't, or Spotify's artificial inteligence, that always recommended me amazing songs.


A year ago, I made the switch because no matter how many times Chromecast support was requested, Spotify wouldn't listen. That, together with a poor GNU/Linux client, made me dislike Spotify.

A year later, Spotify now has Chromecast support,


with a poor GNU/Linux client.


Because I miss some of Spotify's details and they, finally, listened to the community, it's time to come back, hopefully, to stay.


Now, the subject of this post says "[Release]" because I have a 931 song playlist on Google Music and I'm not transfering it manually, so I made a little script.


Tested on Chrome (02-01-2017).

Github \ (The instructions are in the script).

Unportify is a script that exports your Google Play music to text.
Copyright (C) 2016  Arnau Villoslada
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Copy of License:

HOW TO USE (Chrome):
1. Go to Google Play Music
2. Once loaded, right click anywhere in the page and left click on "Inspect" (or press CTRL + SHIFT + I)
3. Go to the "Console" tab.
4. There's a little dropdown, select "top".
5. Paste this script next to the ">" and press enter.
6. You can now close the console. (Click the X at the right or press CTRL + SHIFT + I).
7. Go to the playlist you want to export.
8. Press 0 to start the export process.
   If pressing 0 does nothing, reload the page and make sure you you inject the code on "top" (step #4).
9. Follow on-screen instructions.
10. Once it's done, a file containing your playlist will download, paste it here:

NOTE: If the number of catched songs is lower than the number of songs in your playlist,
      you might have duplicates, if not, try lowering the scroll speed.

/* Pre-start variables */
unportify_url = '';

message = {};
message['startup-1'] = 'Unportify v1.4.3 injected, you can now close this window.';
message['startup-2'] = 'Press 0 at any time to start exporting your playlist.';
message['albums'] = 'Would you like to also export the albums? \n(Not needed for Unportify importer)';
message['scroll'] = 'How fast would you like me to scroll? (Leave at default if you\'re not sure).\n\
Lower if it\'s not catching the entire playlist', '100';

message['scroll-error'] = 'Please, only use numbers.\nDefault should work fine on a average speed Internet connection';
message['scroll-startup'] = 'I\'m going to start scrolling, I\'ll warn you when I\'m done.\nPlease, don\'t switch tabs.';
message['open-unportify'] = 'Would you like to import this to Spotify? \n(Opens ' + unportify_url + ')';


// Sets the starting variables
function startingVariables() {

    // JSON containing playlist
    playlist_string = '{"tracks": [';

    // Google Music uses a main container to scroll instead of the browser window, get it.
    main_container = document.querySelector("#mainContainer");

    // Ask if the user would like to export albums
    export_albums = confirm(message['albums']);

    // This function keeps asking for the scroll speed until a number is given
    function askAutoScrollSpeed() {
        auto_scroll_speed = prompt(message['scroll'], '100');
        if (isNaN(parseInt(auto_scroll_speed))) {

    // Scroll margin. Used to make sure the script detects that the bottom of the page has been reached.
    scroll_margin = 50;


    // Set the scroll speed to the user's desired speed
    auto_scroll_speed = parseInt(auto_scroll_speed);

    // Go to the top of the playlist
    main_container.scrollTop = 0;

    // Set the starting auto scroll position
    auto_scroll_pos = main_container.scrollTop;

// Gets all of the loaded songs from the playlist
function getSongs() {
    var i, j, playlist_div = document.querySelectorAll('.song-row');
    for (i = 0, j = playlist_div.length; i < j; i++) {
        var track = playlist_div[i]; 
        var title = encodeURI(track.querySelector('[data-col="title"] span.column-content').textContent);
        var artist = encodeURI(track.querySelector('[data-col="artist"] span.column-content').textContent);
        var album = encodeURI(track.querySelector('[data-col="album"] span.column-content').textContent);

        // Clean possible characters that could mess up the JSON
        title = title.replace(/"/g, '\\"');
        artist = artist.replace(/"/g, '\\"');
        album = album.replace(/"/g, '\\"');

        // Checks if the album needs to be exported
        if (export_albums) {        
            var song = ('{"artist": "' + artist + '", "title": "' + title + '", "album": "' + album + '"},');
        } else {
            var song = ('{"artist": "' + artist + '", "title": "' + title + '"},');

        // Only add if the song isn't already added the list
        if (playlist_string.includes(song) != true) {
            playlist_string += song;

        // Will contain the last processed song
        last = track;

// As Trump would say, we will do many many things, and it will be great.
function scrollAction() {
    var last_song_pos = last.offsetTop;
    var window_scroll_pos = main_container.scrollTop;

    // Checks if we reached the last loaded song
    if(window_scroll_pos >= last_song_pos) {

    var limit = main_container.scrollHeight - main_container.offsetHeight - scroll_margin;

    // Checks if we arrived at the bottom of the playlist
    if (window_scroll_pos >= limit) {

        // Stops the page scroll

        // Get the playlist name
        var playlist_title = document.querySelector('h2[slot="title"]').textContent;

        // Remove theh last ,
        playlist_string = playlist_string.replace(/.$/, '');

        // Close JSON.
        playlist_string += '], "playlist": "' + playlist_title + '", "exported_by": "Unportify"}';

        // Convert to human readable JSON
        playlist_string = JSON.stringify(JSON.parse(playlist_string), null, 4);

        // Exports the playlist
        alert('Catched ' + (playlist_string.match(/"artist":/g) || []).length + ' songs.');
        var blob = new Blob([playlist_string], {type: 'text/plain;charset=utf-8'});
        saveAs(blob, playlist_title + '.txt');

        if (confirm(message['open-unportify'])) {
  , '_blank');

        // Awaits for the user to press the start key again
        window.onkeypress = function(event){startExport(event)}

// This funcion makes the page scroll automatically.
function pageScroll() {
    main_container.scrollTop = auto_scroll_pos;
    scroll_delay = setTimeout(pageScroll,10);
    auto_scroll_pos = auto_scroll_pos + auto_scroll_speed;

// Starts everything when the user presses the start key.
function startExport(event) {
    if (event.keyCode == 48) {
        window.onkeypress = null;

window.onkeypress = function(event){startExport(event)};

/* Everything below is not my code */

// Used to save the playlists in a file.
/*! @source */
var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("","a"),o="download"in r,i=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},a=/constructor/i.test(e.HTMLElement),f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},d="application/octet-stream",s=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,s)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{,n||e)}catch(i){u(i)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,s){if(!s){t=p(t)}var v=this,w=t.type,m=w===d,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&a)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;;i(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define([],function(){return saveAs})}


Signatures are too generic
60 Replies

Hi TheDegree0,


The .js script works fine and export all songs from my playlist to .txt file. But I have trouble with importation website Unportify provides: I copied content of received .txt file to webpage and pressed "IMPORT", but the importation process was stuck. I have tried several times, but it isn't help. Also I have tried import sample playlist from Format example webpage - importation process also was stuck. I have attached my .txt file playlist. Can you help, please?


Hey desergik,


That's super weird, I was able to import your playlist.

Attached are the tracks that couldn't be found (all remixes and feat. I'll fix this in the near future).


Checking the server logs revealed a bunch of errors with your tokens, they got messed up for some reason. If you ever need to import anything else, go to revoke access to Unportify and try again.


Your playlist:


Signatures are too generic

Would you specify where the code begins and ends?


Would you site where eactly we are to go to the playlist for step 7?


I am asking these two questions because when I press 0 at a playlist nothing happens. I confirmed my set up as instucted in step 8, but still nothing. 





Hey man, looks like a great script! Export worked & produced a pretty JSon (whose form validates when I test it) of 500+ numbers. Running Chrome in Debian. Exported twice with the same end result.


When I run it through the site however, it says "Wrong Format".

Deconecting spotify & reconnecting didn't change anything..


Thanks up front 😉


tldr; it worked when pasting it from LibreOffice, I suppose line terminators **bleep**


So far I opened the file I uploaded via gedit & pasted it. Considering it's being saved on linux & then being uploaded to a service expecting a windows saved file, I supposed it might not have the right line terminators (the whole LF; CR+LF **bleep**). I also figured LibreOffice might do some whatever, so I retested with opening the file with Writer & pasted that content. it worked 🙂 #HappyCamper.


Thanks a lot for the script & the import service; you rock!!

reply to MS191:

Are you using Chrome? I only tested it on that browser.

If you go inside the playlist, where it lists all your songs and then press 0, it should pop up a message to start the importation process.

If that still doesn't work, try starting the importation process and check if there's any error message popping up (scary red message). If it does, paste it here.


Hope this helps,


Signatures are too generic

reply to TheRedGen:

Hello, I'm glad it's fixed, I suspect the problem was with the format of the file that was being saved, it should be UTF-8 but maybe something was messing with it.


I could always add an option to print the JSON on console instead of saving a file, that could solve some problems, in fact, the generated song names and albums look so ugly in an attempt to solve this.


Thanks for the feedback!


Signatures are too generic

Thank you very match for support, TheDegree0!

Hey The Degree0,


Thanks for providing this great tool. I just wanna list two bugs I found:

  • If you want  to export all songs from the Songs tab on Google Play, an exception will occur on line 145: 
    var playlist_title = document.querySelector(' div.title').textContent;
    So I just hard coded the title.
  • When importing the text on the unportify site, I got unexpected errors and it turns out it doesn't like special characters like left brackets which has unicode of %5B. I'm a huge alt-J fan and it didn't like the ∆ either.  

reply to TheUndeniable:


Hey, thanks for the feedback! Unfortunetly I cannot keep updating this script since my Google Play Music subscription has expired. Any edits to the script are welcome!


Regarding the second bug, if you could provide your playlist, I could try fixing it, since it doesn't require a subscription.


If I ever get my hands on a subscription again, the first thing I'll do is update the script 🙂


Signatures are too generic

Hi! Unportify imports empty playlist. Can you look at what can be wrong?

To anyone experiencing problems: on line 145, try replacing 

document.querySelector(' div.title')



This makes the script work for me.

reply to logicnotlogical:

Thank you! Just updated the script.


For everyone still experiencing problems, I'm currently working on a better version that will no longer require you to use this script, everything will be done through the website.


Signatures are too generic

@TheDegree0 wrote:

reply to logicnotlogical:

Thank you! Just updated the script.


For everyone still experiencing problems, I'm currently working on a better version that will no longer require you to use this script, everything will be done through the website.


Hi, TheDegree0.


Firstly, thanks so much for creating this tool - it's really helping me transition back to Spotify!


However for certain playlists I'm encountering an issue that I haven't been able to resolve yet. I'm able to successfully extract the playlist tracks but during import on the Unportify website I get a message stating "An unknown exception has occurred." This results in an empty playlist being imported into Spotify. Have you encountered this before/ know why this error would occur? I've tried disconnecting and reconnecting unportify to Spotify but that didn't fix the issue. I've attached one of the offending playlists if you'd be so kind as to check it out!


Thanks in advance. 






Having issues with the script. I'm running it on a Chromebook (so obviously it's being done in Chrome). I'm pretty tired so I'll attempt again later this week and see what errors are being generated. It seems to execute the first part, and scrape the songs (or it scrolls like it's doing what it should) but it gets to the end of my songlist and just sits there. 

runs well but does not create a .txt file instead i get error message.


listen.js:1161 Uncaught Cx {message: "Error in protected function: saveAs is not defined", Xrb: true, cause: ReferenceError: saveAs is not defined
at scrollAction (<anonymous>:121:9)
at pageScroll (<an…, stack: "ReferenceError: saveAs is not defined↵ at scrol…71dda72389b50b7b2ed6815356eaa/listen.js:1160:261)"}


I am so glad you made this and it looks fantastic, I just need some help getting over this step.

Looking forward to getting a way to get these - at the minute the script looks like it's running but there's just loads of 'Uncaught' red x errors and nothing else.


Thanks for your hard work and efforts on this!

What are the odds that you would be able to figure out how to do this for an entire library? That is something I would most definitely pay for! I tend to switch back and forth between Google Music and Spotify and to be able to move my whole library back and forth would be spectactular! 

You certainly have put in a lot of effort to get this function to work - well done!

I wondered if you knew of a method for doing the reverse - getting Spotify playlists into Google Play? I'm about to set up a Family account in Google Play but fear my daughter won't use it as she has lots of Spotify playlists that she won't give up.

Thanks again,


Yeah, its called protify, that is why this is called unportifiy, sadly though though unportify is not working for me 😞

Suggested posts