If your app needs to let a user pick a file from his Google Drive, you might consider using the Google Picker API.
I recently implemented the Picker for my users because I need to let users choose a Google Sheet from their account, and in some rare cases, Google Drive access is blocked the G Suite Admin, rendering my code that lists Sheets from Google Drive useless. The Picker is an alternative that doesn’t require Drive access, and so it allows my users to pick a spreadsheet from their account, even though Google Drive access may be denied by their G Suite admin.
In implementing the Picker API, I encountered several challenges and subsequently came up with several advanced techniques that you also might find useful.
The main documentation for the Picker API is here. After you get the example provided by Google working, here are a few advanced techniques you might find useful.
Open the Picker in a popup, and return the file’s ID to the main window
In your web app, you might link to the Picker and then open it in a popup. Normally it’s just a matter of simple JavaScript to return a value from the popup to the main window, and many have written about how to do that. What if, however, you’re launching the Picker from a Chrome extension, and the Chrome extension is running on a site for which you don’t have any control, like Gmail. If you use the normal technique to retrieve the value from the popup, you’ll get this error in the Console:
SecurityError: Blocked a frame with origin "http://www.<domain>.com" from accessing a cross-origin frame.
The only known way around this is to use the window.postMessage function to pass a message from the popup to the main window, as explained here. In this case, the message is the ID of the file the user has chosen from his Google Drive, prepended by the string “spreadsheetid—” so that I know the message is from my popup.
Here’s the code from my Picker HTML:
function pickerCallback(data) { if (data.action == google.picker.Action.PICKED) { var fileId = data.docs[0].id; window.opener.postMessage('spreadsheetid---' + fileId, '*'); window.close(); } }
And here’s the code in the parent window.
window.addEventListener('message', function(event) { if (event.data.toString().includes('spreadsheetid---')) { SpreadsheetId = event.data.toString(); } });
Retrieve the file ID without access to any scopes
If you read the Google documentation for the Picker API, you might assume that to use it, you must request access to the “drives” scope.
Google’s documentation even states:
Note: For a list of valid views, refer to ViewId in the Google Picker reference. To obtain the token for any of these views, use the
https://www.googleapis.com/auth/drive.file
scope.
And in the sample code, we find this:
After all, in order to actually do anything with the file, you need to be able to at least read the file, and the various Google Drive scopes is what makes that possible. In some cases, however, you might only care about retrieving the file ID, and in that case, you don’t need access to any scopes. This is the case for my use case, where I’m using the Picker to let a user choose a Google Sheet. Once I have the ID of the Google Sheet, then I use a scope that I’ve requested access to separately as part of the user login process, the spreadsheets.readonly scope as part of the V4 Sheets API, to read the spreadsheet data.
Use an existing OAuth token rather than requiring the user to log in every time
If you follow the Google example for getting the Picker going, you’ll notice that every time you load the picker, an OAuth login is presented to the user. And if you step through the sample code, you’ll see that this code is what triggers the login process:
var oauthToken; // Use the Google API Loader script to load the google.picker script. function loadPicker() { gapi.load('auth', {'callback': onAuthApiLoad}); gapi.load('picker', {'callback': onPickerApiLoad}); } function onAuthApiLoad() { window.gapi.auth.authorize( { 'client_id': clientId, 'scope': scope, 'immediate': false }, handleAuthResult); } function handleAuthResult(authResult) { if (authResult &amp;&amp; !authResult.error) { oauthToken = authResult.access_token; createPicker(); } }
If a user is going to be “picking” a file over and over, though, you don’t want your user to have to log in every time. In this case, you have a couple of options:
- You can store the token value somewhere and then use it the next time. Notice that the only important value from the Google sample above is authResult.access_token. If you store that and then set that the next time you need to load the Picker for a user, you can comment out the code in the “onAuthApiLoad” function.
- If you’re already generating a token value from the initial Google OAuth login to your app, then you can use that token value and never make the user log in before picking a file. Again, comment out the code in the “onAuthApiLoad” function and replace it with your own code that retrieves the token value and manually all handleAuthResult yourself, passing in the token value that you retrieved from your database or perhaps, localStorage. I’m not going to comment on the security of where you store your token values, because that’s beyond the “scope” of this article, but I’m just presenting a couple of options here for the purpose of demonstration.
List just a particular file type in the Picker
This is fairly well presented in the Google documentation, but it’s worth going over anyway. In my case, I only want spreadsheets to be displayed in the picker. So in the picker builder, I need this line of code:
var view = new google.picker.View(google.picker.ViewId.SPREADSHEETS);
Here’s the createPicker function with that filter included.
function createPicker() { if (pickerApiLoaded &amp;&amp; oauthToken) { var view = new google.picker.View(google.picker.ViewId.SPREADSHEETS); var picker = new google.picker.PickerBuilder() .enableFeature(google.picker.Feature.NAV_HIDDEN) .setAppId(appId) .setOAuthToken(oauthToken) .addView(view) .addView(new google.picker.DocsUploadView()) .setDeveloperKey(developerKey) .setCallback(pickerCallback) .build(); picker.setVisible(true); } }
Here’s a list of other types from the documentation you can include to display different file types.
Conclusion
I hope this helps you use the Google Picker API more effectively. Also see my other insights into Google OAuth.
Email marketing. Cold email. Mail merge. Avoid the spam folder. Easy to learn and use. All inside Gmail.
TRY GMASS FOR FREE
Download Chrome extension - 30 second install!
No credit card required
Is it possible to include the google file picker in an extension without having to host the picker html on your own website?
Hello, thank you for the write up. This part is exactly what I want to do: “If you’re already generating a token value from the initial Google OAuth login to your app, then you can use that token value and never make the user log in before picking a file.”
Do you have any example code for that? I am setting the token, I think, but keep getting a black error box that says “In order to select an item from your online storage, please sign in.”
The token is stored by PHP in the session when the user logs into the application. Then I am setting the token string in the javascript code when the picker is being setup. But something is a mismatch. Is there a magic setting in the developer console that has to be enabled to synch the tokens between the plain OAuth itself and the Picker API??
Update… you need to make sure you set the scope when doing initial authentication, to include the Google Drive picker scope. Otherwise the access token from initial authentication is not valid for accessing the Google Drive picker. Kind of obvious.
Hi,
I would like to use picker on my wildcard domain. For example *.somedomain.com. But google requires me to fill in domain from which picker must run. There is no possibility to write it with wildcard in order to run picker from any of my subdomains. so I must write them all.
Is there any work around for this?