Error when creating multiple PDFs in Google Apps Script - pdf

I've created a function that saves a spreadsheet (sheet) as PDF to a specified folder. The function works great but when I run it in multiple times (I need to do it 20 times), I get an error after the 7th, 8th or 9th run. The error is 429. It doesn't give me a whole lot of info and I can't seem to find what the error is and how to correct. I've tried adding a Utilities.sleep(xxx) and it does work when I do a 5 second sleep (but not when it's less than 5 seconds)!
Here's my code (with Utilities.sleep):
/**
* Creates a PDF file
*
* 2019-12-17 Simon: Created
*
* #param {?} token ScriptApp.getOAuthToken();
* #param {?} spreadsheet Spreadsheet (SpreadsheetApp.getActiveSpreadsheet())
* #param {string} sheetName Name of the sheet to print
* #param {string} pdfName Name of the pdf file (excluding .pdf)
* #param {string} folder Folder to save in
* #param {string} portrait true=portrait, false=landscape
* #param {number} scale 1 = Normal 100% -- 2 = Fit to width -- 3 = Fit to height -- 4 = Fit to Page
* #param {number} margins In inches. Dot as decimal separator, e.g. '0.2'
* #param {string} range Optional. E.g. 'D4:AX74'
*/
function savePdf(spreadsheet, sheetName, pdfName, folder, portrait, scale, margins, range) {
var rangeUse = (range ? '&range=' + range : '');
var sheetId = spreadsheet.getSheetByName(sheetName).getSheetId();
var url_base = spreadsheetId.getUrl().replace(/edit$/,'');
var url_ext = 'export?'
+ '&gid=' + sheetId
+ rangeUse
+ '&format=pdf' // export format
+ '&size=a4' // A3/A4/A5/B4/B5/letter/tabloid/legal/statement/executive/folio
+ '&portrait=' + portrait // true = Potrait / false= Landscape
+ '&scale=' + scale // 1 = Normal 100% -- 2 = Fit to width -- 3 = Fit to height -- 4 = Fit to Page
+ '&top_margin=' + margins // all four margins must be set!
+ '&bottom_margin=' + margins // all four margins must be set!
+ '&left_margin=' + margins // all four margins must be set!
+ '&right_margin=' + margins // all four margins must be set!
+ '&gridlines=false' // true/false
+ '&printnotes=false' // true/false
+ '&pageorder=2' // 1 = Down, then over -- 2 = Over, then down
+ '&horizontal_alignment=CENTER' // LEFT/CENTER/RIGHT
+ '&vertical_alignment=MIDDLE' // TOP/MIDDLE/BOTTOM
+ '&printtitle=false' // print title --true/false
+ '&sheetnames=false' // print sheet names -- true/false
+ '&fzr=true' // repeat row headers (frozen rows) on each page -- true/false
+ '&fzc=true' // repeat column headers (frozen columns) on each page -- true/false
+ '&attachment=false' // true/false
var token = ScriptApp.getOAuthToken();
var url_options = {headers: {'Authorization': 'Bearer ' + token, 'muteHttpExceptions': true,}};
Utilities.sleep(5000);
var response = UrlFetchApp.fetch(url_base + url_ext, url_options);
var blob = response.getBlob().getAs('application/pdf').setName(pdfName + '.pdf');
folder.createFile(blob);
}

Changed the script to make a copy of the spreadsheet in the beginning and working on that copy – and trash it at the end.
Here's the final working script:
/**
* Creates a PDF file
*
* 2019-12-17 Simon: Created
*
* #param {?} spreadsheet Spreadsheet (SpreadsheetApp.getActiveSpreadsheet())
* #param {string} sheetName Name of the sheet to print
* #param {string} pdfName Name of the pdf file (excluding .pdf)
* #param {string} folder Folder to save in
* #param {string} portrait true=portrait, false=landscape
* #param {number} scale 1 = Normal 100% -- 2 = Fit to width -- 3 = Fit to height -- 4 = Fit to Page
* #param {number} margins In inches. Dot as decimal separator, e.g. '0.2'
* #param {string} range Optional. E.g. 'D4:AX74'
*/
function savePdf(spreadsheet, sheetName, pdfName, folder, portrait, scale, margins, range) {
var rangeUse = (range ? '&range=' + range : '');
var ssNew = spreadsheet.copy('temp');
var sheetId = spreadsheet.getSheetByName(sheetName).getSheetId();
var url_base = spreadsheet.getUrl().replace(/edit$/,'');
var url_ext = 'export?'
+ '&gid=' + sheetId
+ rangeUse
+ '&format=pdf' // export format
+ '&size=a4' // A3/A4/A5/B4/B5/letter/tabloid/legal/statement/executive/folio
+ '&portrait=' + portrait // true = Potrait / false= Landscape
+ '&scale=' + scale // 1 = Normal 100% -- 2 = Fit to width -- 3 = Fit to height -- 4 = Fit to Page
+ '&top_margin=' + margins // all four margins must be set!
+ '&bottom_margin=' + margins // all four margins must be set!
+ '&left_margin=' + margins // all four margins must be set!
+ '&right_margin=' + margins // all four margins must be set!
+ '&gridlines=false' // true/false
+ '&printnotes=false' // true/false
+ '&pageorder=2' // 1 = Down, then over -- 2 = Over, then down
+ '&horizontal_alignment=CENTER' // LEFT/CENTER/RIGHT
+ '&vertical_alignment=MIDDLE' // TOP/MIDDLE/BOTTOM
+ '&printtitle=false' // print title --true/false
+ '&sheetnames=false' // print sheet names -- true/false
+ '&fzr=true' // repeat row headers (frozen rows) on each page -- true/false
+ '&fzc=true' // repeat column headers (frozen columns) on each page -- true/false
+ '&attachment=false' // true/false
var token = ScriptApp.getOAuthToken();
var url_options = {headers: {'Authorization': 'Bearer ' + token, 'muteHttpExceptions': true,}};
var response = UrlFetchApp.fetch(url_base + url_ext, url_options);
var blob = response.getBlob().getAs('application/pdf').setName(pdfName + '.pdf');
folder.createFile(blob);
DriveApp.getFileById(ssNew.getId()).setTrashed(true);
}

I ran into this once and was able to fix it by removing the call to ScriptApp.getOAuthToken() out of the recursive portion of the function. I think the easiest way to do this in your case with minimal complication would be to use the CacheService.
replace the line var token = ScriptApp.getOAuthToken();
with:
var token;
if(CacheService.getScriptCache().get('token')!=null) {
token = CacheService.getScriptCache().get('token');
} else {
token = ScriptApp.getOAuthToken();
CacheService.getScriptCache().put('token',token,120);
}
This will store the token value in the CacheService instead of calling it recursively with your script loop. Hopefully this solves your issue as it did mine.
Edit:
After the above didn't fix your issue I looked back at what I had done and wrongly assumed that it was fetching the token that had caused my issue, but it was a rate limit on exporting from the Google Sheets API. Here's what I noticed ACTUALLY fixed MY issue at the time:
The rate limit (see my last paragraph for my thoughts on this) is per SHEET and not per user -- my recursive script was accessing two different sheets at the time so the natural delay in the function created just enough time delay for my script to run its course without problems.
Now to a fix for your drama:
After duplicating your problem I modified my parent function to create a copy of the main spreadsheet:
var mainsheetcopy = mainsheet.copy('Copy of main sheet')
and then switch between the two spreadsheets to send the call to the function to extract the PDF. I was able to iterate through the extraction of 20 pdfs with a sleep delay of only 750ms, and 18 iterations with no delay at all built in.
for(var i=0; i<20; i++) {
if(isEven(i)) {
sheetid = mainsheet.getId();
} else {
sheetid = mainsheetcopy.getId()}
for reference, the isEven function is below:
function isEven(n) {
return n % 2 == 0;
}
Then, at the end of the script I deleted the copy:
DriveApp.getFileById(mainsheetcopy.getId()).setTrashed(true);
If time is a factor, you could create a third copy of the main spreadsheet and through only the delay that the function takes naturally it would put you outside of the rate limit that they have on exporting the sheet as a PDF.
The actual rate limit is a bit elusive, but one sheet every 7.5-8 seconds seems to skirt this. I was able to iterate up to 5 PDF files exported per sheet with no rate limit 100% of the time and 6 occasionally.

Related

Create Google Script that saves sheet as pdf to specified folder in google drive

So thanks to other members I was able to piece together a script that does what I need except for 1 small hiccup. Rather than saving the single sheet as a pdf in the designated folder, it saves the entire workbook. I've tried multiple variations to get it to save just the sheet I want (named JSA) but it either gives me an error message or continues to download the whole spreadsheet. I'm sure it's something simple I'm missing, but I'm not as versed with Script as I am with writing Macros, so I'm stuck. The first part of the code, downloading it as a PDF works like a charm, it's the last section where I want it to save the PDF to a specified folder that's the issue. Any assistance would be appreciated.
function downloadPdf() {
var ss = SpreadsheetApp.getActive(),
id = ss.getId(),
sht = ss.getActiveSheet(),
shtId = sht.getSheetId(),
url =
'https://docs.google.com/spreadsheets/d/' +
id +
'/export' +
'?format=pdf&gid=' +
shtId
+ '&size=letter' // paper size legal / letter / A4
+ '&portrait=false' // orientation, false for landscape
+ '&scale=2' // 1= Normal 100% / 2= Fit to width / 3= Fit to height / 4= Fit to Page
+ '&fitw=true' // fit to width, false for actual size
+ '&top_margin=0.25' // All four margins must be set!
+ '&bottom_margin=0.25' // All four margins must be set!
+ '&left_margin=0.25' // All four margins must be set!
+ '&right_margin=0.25' // All four margins must be set!
+ '&sheetnames=false&printtitle=false' // hide optional headers and footers
+ '&pagenum=false&gridlines=false' // hide page numbers and gridlines
+ '&fzr=false' // do not repeat row headers (frozen rows) on each page
+ '&horizontal_alignment=CENTER' //LEFT/CENTER/RIGHT
+ '&vertical_alignment=TOP'; //TOP/MIDDLE/
var val = SpreadsheetApp.getActive().getRange('H2').getValues();//custom pdf name here
var val2= Utilities.formatDate(SpreadsheetApp.getActive().getRange("N2").getValue(), ss.getSpreadsheetTimeZone(), "MM/dd/YY");
var val3= " - "
val += val3 += val2 += '.pdf';
//can't download with a different filename directly from server
//download and remove content-disposition header and serve as a dataURI
//Use anchor tag's download attribute to provide a custom filename
var res = UrlFetchApp.fetch(url, {
headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() },
});
SpreadsheetApp.getUi().showModelessDialog(
HtmlService.createHtmlOutput(
'<a target ="_blank" download="' +
val +
'" href = "data:application/pdf;base64,' +
Utilities.base64Encode(res.getContent()) +
'">Click here</a> to download, if download did not start automatically' +
'<script> \
var a = document.querySelector("a"); \
a.addEventListener("click",()=>{setTimeout(google.script.host.close,10)}); \
a.click(); \
</script>'
).setHeight(50),
'Downloading PDF..'
);
const folderName = `Test`;
const fileNamePrefix = val += val3 += val2 += '.pdf';
var JSA = SpreadsheetApp.getActiveSpreadsheet();
var s = JSA.getSheetByName("JSA");
DriveApp.getFoldersByName(folderName)
.next()
.createFile(SpreadsheetApp.getActiveSpreadsheet()
.getBlob()
.getAs(`application/pdf`)
.setName(`${fileNamePrefix}`));
}
I believe your goal is as follows.
You want to create a PDF file, which is downloaded using the first part of your script, in the specific folder on Google Drive.
In this case, how about the following modification? I thought that res might be able to be used.
From:
var JSA = SpreadsheetApp.getActiveSpreadsheet();
var s = JSA.getSheetByName("JSA");
DriveApp.getFoldersByName(folderName)
.next()
.createFile(SpreadsheetApp.getActiveSpreadsheet()
.getBlob()
.getAs(`application/pdf`)
.setName(`${fileNamePrefix}`));
To:
DriveApp.getFoldersByName(folderName)
.next()
.createFile(res.getBlob().setName(`${fileNamePrefix}`));
In this modification, it supposes that sht = ss.getActiveSheet(), is JSA sheet. If the active sheet is not JSA sheet, please modify as follows.
To:
const url2 =
'https://docs.google.com/spreadsheets/d/' +
id +
'/export' +
'?format=pdf&gid=' +
ss.getSheetByName("JSA").getSheetId()
+ '&size=letter' // paper size legal / letter / A4
+ '&portrait=false' // orientation, false for landscape
+ '&scale=2' // 1= Normal 100% / 2= Fit to width / 3= Fit to height / 4= Fit to Page
+ '&fitw=true' // fit to width, false for actual size
+ '&top_margin=0.25' // All four margins must be set!
+ '&bottom_margin=0.25' // All four margins must be set!
+ '&left_margin=0.25' // All four margins must be set!
+ '&right_margin=0.25' // All four margins must be set!
+ '&sheetnames=false&printtitle=false' // hide optional headers and footers
+ '&pagenum=false&gridlines=false' // hide page numbers and gridlines
+ '&fzr=false' // do not repeat row headers (frozen rows) on each page
+ '&horizontal_alignment=CENTER' //LEFT/CENTER/RIGHT
+ '&vertical_alignment=TOP'; //TOP/MIDDLE/
const res2 = UrlFetchApp.fetch(url2, {
headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() },
});
DriveApp.getFoldersByName(folderName)
.next()
.createFile(res2.getBlob().setName(`${fileNamePrefix}`));
Reference:
fetch(url, params)

Google Docs PDF Export Adds Extra Blank Pages at the End

I use google spreadsheet to create invoices and send them by email in PDF format, it's been working great, my spreadsheets are yearly base, so every new year I create another one, all my spreadsheet from 2021 are still working fine, but all the new ones I've created for 2022 when I try to print or export PDFs, extra blank pages are beeing added to the end of the document. The number of blank pages are dependent on the number of blank lines on the sheet, but I didn't have this problem before, before even when the sheet has 1000 lines, if the invoice has only 1 page the PDF/Print will only generate 1 page, now if the sheet has 1000 lines it generates 19 blank pages.
Anyone with same problem or a solution?
Link for example: Spreadsheet link
Try to print or download the page and you will see the blank pages generated.
function savePDF() {
const ssh = SpreadsheetApp.getActiveSpreadsheet();
const invoice = ssh.getSheetByName('invoice');
const request = {
"method": "GET",
"headers":{"Authorization": "Bearer
"+ScriptApp.getOAuthToken()},
}
const key = ssh.getId();
const bogus = DriveApp.getRootFolder();
const fetch='https://docs.google.com/spreadsheets/d/'
+ key
+'/export?format=pdf&gid='
+ invoice.getSheetId()
+
'&size=letter&portrait=true
&printtitle=false
&pagenum=CENTER
&sheetnames=false
&gridlines=false
&top_margin=0.25
&bottom_margin=0.50&left_margin=0.25
&right_margin=0.25';
const name = "invoice.pdf";
let pdf = UrlFetchApp.fetch(fetch, request);
pdf = pdf.getBlob().setName(name);
const fold = "Folder ID goes here";
const folder = DriveApp.getFolderById(fold);
const file = folder.createFile(pdf);
}
The issue is that it prints the whole sheet by default. So it includes all those blank lines, thus adding blank sheets.
What you can do as a workaround is to CTRL + A in the sheet, then export using Selected cells
Export:
As you can see, total number of pages is only 1.
Script:
I'm not sure why it just doesn't work right now, but there might have been some changes and defaulted to export the whole sheet. To bypass this issue, you will need to add the actual range to the link. See modifications below:
Modifications:
Get the A1 notation of the data range:
const ssh = SpreadsheetApp.getActiveSpreadsheet();
const invoice = ssh.getSheetByName('invoice');
// add this line
const range = invoice.getDataRange().getA1Notation();
Append the range into the link:
const fetch = 'https://docs.google.com/spreadsheets/d/' + key
+ '/export?format=pdf&gid=' + invoice.getSheetId()
+ '&size=letter'
+ '&portrait=true'
+ '&printtitle=false'
+ '&pagenum=CENTER'
+ '&sheetnames=false'
+ '&gridlines=false'
+ '&top_margin=0.25'
+ '&bottom_margin=0.50'
+ '&left_margin=0.25'
+ '&right_margin=0.25'
// add range to link
+ '&range=' + range;
Output:

PDF margins - Google Script

I have the basic script working fine, it does exactly what I want it to do. It takes the Google Sheet turns it into a PDF and emails me the PDF.
My question is how can I adjust the margins on the PDF, I need to set the PDF to fit page. I can't just resize the sheet because it throws the spacing off.
/* Email Google Spreadsheet as PDF */
function PDF() {
// Send the PDF of the spreadsheet to this email address
var email = "gmail.com";
// Get the currently active spreadsheet URL (link)
var ss = SpreadsheetApp.openByUrl(
'https://docs.google.com');
// Subject of email message
var subject = "PAR - " + ss.getRange("A6:A6").getValue() +" - "+ ss.getRange("A5:A5").getValue();
// Email Body can be HTML too
var body = "Name - " + ss.getRange("A6:A6").getValue() +" - "+ ss.getRange("A5:A5").getValue();
var blob = DriveApp.getFileById(ss.getId()).getAs("application/pdf");
blob.setName("Name - " + ss.getRange("A6:A6").getValue() +" - "+ ss.getRange("A5:A5").getValue() + ".pdf");
// If allowed to send emails, send the email with the PDF attachment
if (MailApp.getRemainingDailyQuota() > 0)
GmailApp.sendEmail(email, subject, body, {
htmlBody: body,
attachments:[blob]
});
}
I've seen script like this but cant figure out how to get it to work on my script.
var url_ext = 'exportFormat=pdf&format=pdf' // export as pdf / csv / xls / xlsx
+ '&size=letter' // paper size legal / letter / A4
+ '&portrait=false' // orientation, false for landscape
+ '&fitw=true&source=labnol' // fit to page width, false for actual size
+ '&sheetnames=false&printtitle=false' // hide optional headers and footers
+ '&pagenumbers=false&gridlines=false' // hide page numbers and gridlines
+ '&fzr=false' // do not repeat row headers (frozen rows) on each page
+ '&gid='; // the sheet's Id
I've got the same issue, I also want to remove the margins. It is is any help, here is my working script, which includes the part you are mentioning above.
But I don't see a parameter where you can adjust the margins...
function CreaPDF() {
var report = SpreadsheetApp.getActive();
var pdfName = "ReportXXX";
var sheetName = "Sheet1";
var sourceSheet = report.getSheetByName(sheetName);
SpreadsheetApp.getActiveSpreadsheet().toast('Creating the PDF');
// export url
var url = 'https://docs.google.com/spreadsheets/d/'+report.getId()+'/export?exportFormat=pdf&format=pdf' // export as pdf / csv / xls / xlsx
+ '&size=A4' // paper size legal / letter / A4
+ '&portrait=false' // orientation, false for landscape
+ '&fitw=true' // fit to page width, false for actual size
+ '&sheetnames=false&printtitle=false' // hide optional headers and footers
+ '&pagenumbers=false&gridlines=false' // hide page numbers and gridlines
+ '&fzr=false' // do not repeat row headers (frozen rows) on each page
+ '&gid='+sourceSheet.getSheetId(); // the sheet's Id
var token = ScriptApp.getOAuthToken();
// request export url
var response = UrlFetchApp.fetch(url, {
headers: {
'Authorization': 'Bearer ' + token
}
});
var theBlob = response.getBlob().setName(pdfName+'.pdf');
//var attach = {fileName:'Monthly Report.pdf',content:pdf, mimeType:'application/pdf'};
var name = report.getRange("H1:H1").getValues(); // Get Name
var emailTo = report.getRange("H2:H2").getValues(); // Get email
var period = report.getRange("H3:H3").getValues(); // Get Reporting Period
var subject = " - TEST Monthly Report - " + period; // Construct the Subject Line
var message = "Hi " + name + ", here is your latest report for " + period; // email body text
// Send the freshly constructed email
MailApp.sendEmail(emailTo, subject, message, {attachments:[theBlob]});
}

Convert all sheets to PDF with Google Apps Script

I'm trying to convert a Google spreadsheet with multiple sheets to a PDF file. The script below works, but it only creates a PDF with the last page of the spreadsheet.
function savePDFs() {
SpreadsheetApp.flush();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var url = ss.getUrl();
//remove the trailing 'edit' from the url
url = url.replace(/edit$/,'');
//additional parameters for exporting the sheet as a pdf
var url_ext = 'export?exportFormat=pdf&format=pdf' + //export as pdf
//below parameters are optional...
'&size=letter' + //paper size
'&portrait=false' + //orientation, false for landscape
'&fitw=true' + //fit to width, false for actual size
'&sheetnames=false&printtitle=false&pagenumbers=false' + //hide optional
'&gridlines=false' + //false = hide gridlines
'&fzr=false' + //do not repeat row headers (frozen rows) on each page
'&gid='; //leave ID empty for now, this will be populated in the FOR loop
var token = ScriptApp.getOAuthToken();
//make an empty array to hold your fetched blobs
var blobs = [];
//.fetch is called for each sheet, the response is stored in var blobs[]
for(var i = 0; i < sheets.length; i++) {
var sheetname = sheets[i].getName();
//if the sheet is one that you don't want to process,
//continue' tells the for loop to skip this iteration of the loop
if(sheetname == "Team Member Numbers")
continue;
//grab the blob for the sheet
var response = UrlFetchApp.fetch(url + url_ext + sheets[i].getSheetId(), {
headers: {
'Authorization': 'Bearer ' + token
}
});
//convert the response to a blob and store in our array
blobs.push(response.getBlob().setName(sheets[i].getName() + '.pdf'));
var array_blob = response.getBlob().setName(sheets[i].getName() + '.pdf');
}
//from here you should be able to use and manipulate the blob to send and
//email or create a file per usual.
// send email
var subject = "Enter Subject"
var message = "See attached PDF"
MailApp.sendEmail("email addy here", subject, message,{attachments:[array_blob]});
}
I've tweaked #Mogsdad code slightly to print the entire spreadsheet as one PDF. The key is tweaking the export parameter. Basically replace
'&gid=' + sheet.getSheetId() //the sheet's Id
with
(optSheetId ? ('&gid=' + sheet.getSheetId()) : ('&id=' + ss.getId())) // Print either the entire Spreadsheet or the specified sheet if optSheetId is provided
So the code above minus the looping looks like:
function savePDFs( optSSId, optSheetId ) {
// If a sheet ID was provided, open that sheet, otherwise assume script is
// sheet-bound, and open the active spreadsheet.
var ss = (optSSId) ? SpreadsheetApp.openById(optSSId) : SpreadsheetApp.getActiveSpreadsheet();
// Get folder containing spreadsheet, for later export
var parents = DriveApp.getFileById(ss.getId()).getParents();
if (parents.hasNext()) {
var folder = parents.next();
}
else {
folder = DriveApp.getRootFolder();
}
//additional parameters for exporting the sheet as a pdf
var url_ext = 'export?exportFormat=pdf&format=pdf' //export as pdf
// Print either the entire Spreadsheet or the specified sheet if optSheetId is provided
+ (optSheetId ? ('&gid=' + sheet.getSheetId()) : ('&id=' + ss.getId()))
// following parameters are optional...
+ '&size=letter' // paper size
+ '&portrait=true' // orientation, false for landscape
+ '&fitw=true' // fit to width, false for actual size
+ '&sheetnames=false&printtitle=false&pagenumbers=false' //hide optional headers and footers
+ '&gridlines=false' // hide gridlines
+ '&fzr=false'; // do not repeat row headers (frozen rows) on each page
var options = {
headers: {
'Authorization': 'Bearer ' + ScriptApp.getOAuthToken()
}
}
var response = UrlFetchApp.fetch("https://docs.google.com/spreadsheets/" + url_ext, options);
var blob = response.getBlob().setName(ss.getName() + '.pdf');
//from here you should be able to use and manipulate the blob to send and email or create a file per usual.
//In this example, I save the pdf to drive
folder.createFile(blob);
}
Btw, thank you -- I've been looking for a solution for this for a long time!
This function is an adaptation of a script provided by "ianshedd..." here.
It:
Generates PDFs of ALL sheets in a spreadsheet, and stores them in the same folder containing the spreadsheet. (It assumes there's just one folder doing that, although Drive does allow multiple containment.)
Names pdf files with Spreadsheet & Sheet names.
Uses the Drive service (DocsList is deprecated.)
Can use an optional Spreadsheet ID to operate on any sheet. By default, it expects to work on the "active spreadsheet" containing the script.
Needs only "normal" authorization to operate; no need to activate advanced services (well... you do need some, see this) or fiddle with oAuthConfig.
OAuth2 Authorization for the fetch() call that retrieves the PDF of a spreadsheet is granted via ScriptApp.getOAuthToken(), which gives us the OAuth 2.0 access token for the current user.
With a bit of research and effort, you could hook up to an online PDF Merge API, to generate a single PDF file. Barring that, and until Google provides a way to export all sheets in one PDF, you're stuck with separate files. See Gilbert's tweak for a way to get multiple sheets!
Script:
/**
* Export one or all sheets in a spreadsheet as PDF files on user's Google Drive,
* in same folder that contained original spreadsheet.
*
* Adapted from https://code.google.com/p/google-apps-script-issues/issues/detail?id=3579#c25
*
* #param {String} optSSId (optional) ID of spreadsheet to export.
* If not provided, script assumes it is
* sheet-bound and opens the active spreadsheet.
* #param {String} optSheetId (optional) ID of single sheet to export.
* If not provided, all sheets will export.
*/
function savePDFs( optSSId, optSheetId ) {
// If a sheet ID was provided, open that sheet, otherwise assume script is
// sheet-bound, and open the active spreadsheet.
var ss = (optSSId) ? SpreadsheetApp.openById(optSSId) : SpreadsheetApp.getActiveSpreadsheet();
// Get URL of spreadsheet, and remove the trailing 'edit'
var url = ss.getUrl().replace(/edit$/,'');
// Get folder containing spreadsheet, for later export
var parents = DriveApp.getFileById(ss.getId()).getParents();
if (parents.hasNext()) {
var folder = parents.next();
}
else {
folder = DriveApp.getRootFolder();
}
// Get array of all sheets in spreadsheet
var sheets = ss.getSheets();
// Loop through all sheets, generating PDF files.
for (var i=0; i<sheets.length; i++) {
var sheet = sheets[i];
// If provided a optSheetId, only save it.
if (optSheetId && optSheetId !== sheet.getSheetId()) continue;
//additional parameters for exporting the sheet as a pdf
var url_ext = 'export?exportFormat=pdf&format=pdf' //export as pdf
+ '&gid=' + sheet.getSheetId() //the sheet's Id
// following parameters are optional...
+ '&size=letter' // paper size
+ '&portrait=true' // orientation, false for landscape
+ '&fitw=true' // fit to width, false for actual size
+ '&sheetnames=false&printtitle=false&pagenumbers=false' //hide optional headers and footers
+ '&gridlines=false' // hide gridlines
+ '&fzr=false'; // do not repeat row headers (frozen rows) on each page
var options = {
headers: {
'Authorization': 'Bearer ' + ScriptApp.getOAuthToken()
}
}
var response = UrlFetchApp.fetch(url + url_ext, options);
var blob = response.getBlob().setName(ss.getName() + ' - ' + sheet.getName() + '.pdf');
//from here you should be able to use and manipulate the blob to send and email or create a file per usual.
//In this example, I save the pdf to drive
folder.createFile(blob);
}
}
/**
* Dummy function for API authorization only.
* From: https://stackoverflow.com/a/37172203/1677912
*/
function forAuth_() {
DriveApp.getFileById("Just for authorization"); // https://code.google.com/p/google-apps-script-issues/issues/detail?id=3579#c36
}
I do not yet have the reputation to comment, but there seems to be a minor issue with the top answer above as submitted by Gilbert W... though it's just as likely that I failed to understand something.
That solution includes the line
+ (optSheetId ? ('&gid=' + sheet.getSheetId()) : ('&id=' + ss.getId()))
However, "sheet" has not been defined in the code prior to this point. In Mogsdad's code, "sheet" is defined within the loop that was removed:
for (var i=0; i<sheets.length; i++) {
var sheet = sheets[i];
And "sheets" was defined as
var sheets = ss.getSheets();
The solution works for someone who wants to print the entire spreadsheet, which is the question that was asked. However, the code no longer works for someone who wants to print a single page.
Another issue with Gilbert's updated code was that the HTML request included a reference to the sheet ID, but not the spreadsheet itself. This caused the response to fail if you provided a specific sheet ID, though it works fine if no sheet ID was provided. I got it to work again by reverting the URL base to the way Mogsdad had it.
Another tweak: Gilbert's code automatically names the new .PDF as whatever the spreadsheet was named. Meanwhile, Mogsdad's code prints out every sheet one at a time, naming each .PDF with the spreadsheet name followed by the name of the current sheet. I wanted to print the PDF with the name of the single sheet, if applicable, and also provide the user the ability to specify an output name.
Since no method exists to "getSheetById", depending on the context of your code, it probably makes more sense for the function to take "optSheetName" instead of "optSheetID." The sheet ID can be grabbed from "getSheetByName" if needed, and it seems to me a user is generally more likely to have the sheet's name than the sheet's ID. Both the name and the ID can be obtained programmatically from a bound script, but only the name can be used to get a specific existing sheet.
I also added an optional email parameter so that you can print and email the PDF at the same time.
Here is my version:
function savePDFs( optSSId , optSheetName , optOutputName, optEmail) {
// If a sheet ID was provided, open that sheet, otherwise assume script is
// sheet-bound, and open the active spreadsheet.
var ss = (optSSId) ? SpreadsheetApp.openById(optSSId) : SpreadsheetApp.getActiveSpreadsheet();
var optSheetId = ss.getSheetByName(optSheetName).getSheetId();
var outputName = (optOutputName ? optOutputName : (optSheetName ? optSheetName : ss.getName()))
// Get folder containing spreadsheet, for later export
var parents = DriveApp.getFileById(ss.getId()).getParents();
if (parents.hasNext()) {
var folder = parents.next();
}
else {
folder = DriveApp.getRootFolder();
}
var url_base = ss.getUrl().replace(/edit$/,'');
//additional parameters for exporting the sheet as a pdf
var url_ext = 'export?exportFormat=pdf&format=pdf' //export as pdf
// Print either the entire Spreadsheet or the specified sheet if optSheetId is provided
+ (optSheetId ? ('&gid=' + optSheetId) : ('&id=' + ss.getId())) // Print either the entire Spreadsheet or the specified sheet if optSheetId is provided
// following parameters are optional...
+ '&size=letter' // paper size
+ '&portrait=true' // orientation, false for landscape
+ '&fitw=true' // fit to width, false for actual size
+ '&sheetnames=false&printtitle=false&pagenumbers=false' //hide optional headers and footers
+ '&gridlines=false' // hide gridlines
+ '&fzr=false'; // do not repeat row headers (frozen rows) on each page
var options = {
headers: {
'Authorization': 'Bearer ' + ScriptApp.getOAuthToken(),
}
}
var response = UrlFetchApp.fetch(url_base + url_ext, options);
var blob = response.getBlob().setName((outputName)+ '.pdf');
folder.createFile(blob);
GmailApp.sendEmail(optEmail, "Here is a file named " + outputName, "Please let me know if you have any questions or comments.", {attachments:blob});
}
Here's my variation on this theme, based on Dr Queso's answer.
All parameters (described in the code) are optional and if none are specified it uses the active spreadsheet, converts all of the tabs into a single PDF named after the spreadsheet and doesn't email the PDF.
function test() {
// Create a PDF containing all the tabs in the active spreadsheet, name it
// after the spreadsheet, and email it
convertSpreadsheetToPdf('user#email.com')
// Create a PDF containing all the tabs in the spreadsheet specified, name it
// after the spreadsheet, and email it
convertSpreadsheetToPdf('user#email.com', '1r9INcnsyvSQmeduJWVYAvznOOYei9jeAjsy0acA3G1k')
// Create a PDF just containing the tab 'Sheet2' in the active spreadsheet, specify a name, and email it
convertSpreadsheetToPdf('user#email.com', null, 'Sheet2', 'PDF 3')
}
/*
* Save spreadsheet as a PDF
*
* #param {String} email Where to send the PDF [OPTIONAL]
* #param {String} spreadsheetId Or the active spreadsheet[OPTIONAL]
* #param {String} sheetName The tab to output [OPTIONAL]
* #param {String} PdfName [OPTIONAL]
*/
function convertSpreadsheetToPdf(email, spreadsheetId, sheetName, pdfName) {
var spreadsheet = spreadsheetId ? SpreadsheetApp.openById(spreadsheetId) : SpreadsheetApp.getActiveSpreadsheet();
spreadsheetId = spreadsheetId ? spreadsheetId : spreadsheet.getId()
var sheetId = sheetName ? spreadsheet.getSheetByName(sheetName).getSheetId() : null;
var pdfName = pdfName ? pdfName : spreadsheet.getName();
var parents = DriveApp.getFileById(spreadsheetId).getParents();
var folder = parents.hasNext() ? parents.next() : DriveApp.getRootFolder();
var url_base = spreadsheet.getUrl().replace(/edit$/,'');
var url_ext = 'export?exportFormat=pdf&format=pdf' //export as pdf
// Print either the entire Spreadsheet or the specified sheet if optSheetId is provided
+ (sheetId ? ('&gid=' + sheetId) : ('&id=' + spreadsheetId))
// following parameters are optional...
+ '&size=letter' // paper size
+ '&portrait=true' // orientation, false for landscape
+ '&fitw=true' // fit to width, false for actual size
+ '&sheetnames=false&printtitle=false&pagenumbers=false' //hide optional headers and footers
+ '&gridlines=false' // hide gridlines
+ '&fzr=false'; // do not repeat row headers (frozen rows) on each page
var options = {
headers: {
'Authorization': 'Bearer ' + ScriptApp.getOAuthToken(),
}
}
var response = UrlFetchApp.fetch(url_base + url_ext, options);
var blob = response.getBlob().setName(pdfName + '.pdf');
folder.createFile(blob);
if (email) {
var mailOptions = {
attachments:blob
}
MailApp.sendEmail(
email,
"Here is a file named " + pdfName,
"Please let me know if you have any questions or comments.",
mailOptions);
}
} // convertSpreadsheetToPdf()
NOTE: This doesn't work if the sheet is hidden. Use activate() to unhide a sheet.

Google Apps script created file is not PDF

I'm trying to create a PDF file with this code:
var name = "test.pdf";
var url = 'https://docs.google.com/spreadsheets/d/' + newSpreadsheetID + '/export?exportFormat=pdf&format=pdf' +
'&size=A4' +
'&portrait=true' +
'&fitw=true' + // fit to width, false for actual size
'&sheetnames=false&printtitle=false&pagenumbers=false' +
'&gridlines=false' +
'&fzr=false' + // do not repeat frozen rows on each page
'&gid='+SheetID; //the sheet's Id
var pdf = UrlFetchApp.fetch(url);
var pdf = pdf.getBlob().setName(name);
DocsList.createFile(pdf);
This code creates a file but the file contains just plain text, can you guys help me to get it into PDF format?
First off, DocsList was deprecated (https://developers.google.com/apps-script/reference/docs-list/docs-list). So I think we should use DriveApp this time.
I ignored the parameters you used to format the pdf, the script can be simply as -
var sheet = DriveApp.getFileById(spreadsheetId);
DriveApp.createFile(sheet.getAs("application/pdf"));
Please let me know if you want more elaboration. :)
I made a few changes and got your call working. I don't know where the documentation for the export settings are, but some of them don't work such as "gridlines". Also below is a 2 line example of the same thing.
function makePDF(){
var newSpreadsheetID = SpreadsheetApp.getActive().getId()
var SheetID="Sheet1";
var name = "test.pdf";
var url = 'https://docs.google.com/spreadsheets/d/' + newSpreadsheetID + '/export?exportFormat=pdf&format=pdf'; +
'&size=A4' +
'&portrait=true' +
'&fitw=true' + // fit to width, false for actual size
'&sheetnames=false&printtitle=false&pagenumbers=false' +
'&gridlines=false' +
'&fzr=false'; + // do not repeat frozen rows on each page
'&gid='+SheetID; //the sheet's Id
var pdf = UrlFetchApp.fetch(url,{headers: {authentication:"Bearer " + ScriptApp.getOAuthToken()}, muteHttpExceptions:true}).getBlob();
pdf.setName(name);
DriveApp.createFile(pdf);
}
function simplePDFConvert(){
var ss = SpreadsheetApp.openById( spreadSheetId );
DriveApp.createFile(ss.getBlob());
}