Google Sheets is where a lot of data lives. Invoices, reports, inventory lists, expense summaries: if the data is already in a spreadsheet, the next question is usually "how do I get a clean PDF out of this?"
The answer depends on what you need:
| Method | Effort | Automation | Design control |
|---|---|---|---|
| Built-in export | None | Manual only | Limited |
| Apps Script | Low | Fully automated | Medium |
| Zapier / Make | Low | Fully automated | Medium |
| PDF API (PDF4.dev) | Medium | Fully automated | Full |
Method 1: Built-in export (manual)
The simplest option. No code, no setup.
- Open your spreadsheet in Google Sheets
- Go to File → Download → PDF Document (.pdf)
- Adjust page size, margins, and range in the dialog
- Click Export
This works for one-off exports. The PDF looks like the spreadsheet — grid lines, cell formatting, whatever is visible on screen. You can't fully control headers, footers, or typography.
Limitations: Manual only, no automation, limited design control.
Method 2: Apps Script (automated export)
Apps Script runs JavaScript inside Google Workspace. It can export any sheet to PDF and email or save it automatically.
Basic export to email
Open your spreadsheet, then go to Extensions → Apps Script and paste:
function exportSheetAsPDF() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheetId = ss.getActiveSheet().getSheetId();
const ssId = ss.getId();
// Build the export URL
const url = `https://docs.google.com/spreadsheets/d/${ssId}/export` +
`?format=pdf` +
`&size=A4` +
`&portrait=true` +
`&fitw=true` + // fit to page width
`&gridlines=false` + // hide grid lines
`&printtitle=false` +
`&sheetnames=false` +
`&gid=${sheetId}`;
const token = ScriptApp.getOAuthToken();
const response = UrlFetchApp.fetch(url, {
headers: { Authorization: `Bearer ${token}` }
});
const pdfBlob = response.getBlob().setName('report.pdf');
// Email it
GmailApp.sendEmail(
'[email protected]',
'Monthly report',
'Your report is attached.',
{ attachments: [pdfBlob] }
);
}Save to Google Drive instead
Replace the GmailApp call with:
const folder = DriveApp.getFolderById('YOUR_FOLDER_ID');
folder.createFile(pdfBlob);Export a specific range
Add the range parameter to the URL:
`&range=A1:F50` // only rows 1-50, columns A-FSchedule it automatically
In Apps Script, click Triggers (clock icon) → Add Trigger:
- Function:
exportSheetAsPDF - Event source: Time-driven
- Type: Day timer, Week timer, or Month timer
This runs your export at whatever cadence you need — daily reports, weekly summaries, monthly invoices — without touching anything manually.
Key URL parameters
| Parameter | Values | What it does |
|---|---|---|
format | pdf | Output format |
size | A4, letter, A3 | Page size |
portrait | true / false | Orientation |
fitw | true / false | Fit to page width |
gridlines | true / false | Show/hide grid lines |
gid | Sheet ID (number) | Which sheet to export |
range | A1:G30 | Export a specific range |
top_margin | 0.5 | Top margin in inches |
Method 3: Zapier or Make (no-code)
If your data flows through other tools — a CRM, a form, a database — Zapier and Make can bridge Google Sheets to a PDF generator without any code.
With Zapier
- Trigger: "New or Updated Row in Google Sheets"
- Action: Use the Webhooks by Zapier step to POST your row data to the PDF4.dev API
- Action: Save the PDF to Google Drive or email it
With Make (formerly Integromat)
Make has a visual canvas where you connect modules:
- Google Sheets module — watch for new rows
- HTTP module — POST to PDF4.dev API with the row data as JSON
- Google Drive module — save the returned PDF
- Gmail module — email it (optional)
This approach works well when the PDF is one step in a larger workflow — for example, when a form submission creates a row in Sheets and should immediately trigger a confirmation PDF.
Method 4: PDF API (for styled reports)
The built-in export and Apps Script give you a spreadsheet-looking PDF. If you need a branded report — custom fonts, logos, charts, non-grid layouts — the cleanest approach is:
- Fetch the sheet data via the Google Sheets API
- Inject it into an HTML template
- Render it with a PDF API
This is what PDF4.dev is built for.
Step 1: Fetch sheet data
// Using the Sheets API v4
const SHEET_ID = 'your_spreadsheet_id';
const RANGE = 'Sheet1!A1:E20';
const API_KEY = 'your_google_api_key';
const response = await fetch(
`https://sheets.googleapis.com/v4/spreadsheets/${SHEET_ID}/values/${RANGE}?key=${API_KEY}`
);
const data = await response.json();
// data.values is a 2D array: [[header1, header2], [row1col1, row1col2], ...]
const [headers, ...rows] = data.values;
const items = rows.map(row =>
Object.fromEntries(headers.map((h, i) => [h, row[i] ?? '']))
);Step 2: Define an HTML template
Create a template in your PDF4.dev dashboard, or inline it:
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: 'Inter', sans-serif; padding: 40px; color: #1a1a1a; }
h1 { font-size: 24px; margin-bottom: 8px; }
.meta { color: #666; font-size: 14px; margin-bottom: 32px; }
table { width: 100%; border-collapse: collapse; }
th { background: #f5f5f5; padding: 10px 12px; text-align: left; font-size: 13px; }
td { padding: 10px 12px; border-bottom: 1px solid #eee; font-size: 14px; }
.total { font-weight: 600; }
</style>
</head>
<body>
<h1>{{title}}</h1>
<p class="meta">Generated {{date}}</p>
<table>
<thead>
<tr>
{{#each headers}}<th>{{this}}</th>{{/each}}
</tr>
</thead>
<tbody>
{{#each rows}}
<tr>
{{#each this}}<td>{{this}}</td>{{/each}}
</tr>
{{/each}}
</tbody>
</table>
</body>
</html>Step 3: Render with PDF4.dev
const pdfResponse = await fetch('https://pdf4.dev/api/v1/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.PDF4DEV_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
html: template,
data: {
title: 'Q1 Sales Report',
date: new Date().toLocaleDateString(),
headers,
rows: items.map(item => Object.values(item))
},
options: {
format: 'A4',
margin: { top: '20mm', bottom: '20mm', left: '20mm', right: '20mm' }
}
})
});
const pdfBuffer = await pdfResponse.arrayBuffer();
// Save to disk, upload to S3, email as attachment...Full pipeline: Sheets → PDF → email (Node.js)
import { google } from 'googleapis';
import nodemailer from 'nodemailer';
async function generateReportFromSheets(spreadsheetId, range, recipientEmail) {
// 1. Authenticate with Google
const auth = new google.auth.GoogleAuth({
keyFile: 'service-account.json',
scopes: ['https://www.googleapis.com/auth/spreadsheets.readonly']
});
const sheets = google.sheets({ version: 'v4', auth });
// 2. Fetch sheet data
const { data } = await sheets.spreadsheets.values.get({
spreadsheetId,
range
});
const [headers, ...rows] = data.values;
// 3. Generate PDF
const pdf = await fetch('https://pdf4.dev/api/v1/generate', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PDF4DEV_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
html: buildReportTemplate(headers, rows),
options: { format: 'A4' }
})
});
const pdfBuffer = Buffer.from(await pdf.arrayBuffer());
// 4. Email it
const transporter = nodemailer.createTransport({ /* your SMTP config */ });
await transporter.sendMail({
from: '[email protected]',
to: recipientEmail,
subject: 'Weekly report',
text: 'Your report is attached.',
attachments: [{ filename: 'report.pdf', content: pdfBuffer }]
});
}Choosing the right method
| Situation | Best method |
|---|---|
| One-off export, no automation needed | Built-in export |
| Scheduled email with no external tools | Apps Script |
| Multi-step workflow with other apps | Zapier / Make |
| Branded reports with custom design | PDF API (PDF4.dev) |
| High-volume generation (100s/day) | PDF API (PDF4.dev) |
| Serverless / Next.js / Node.js app | PDF API (PDF4.dev) |
Apps Script is the right choice for most Google Workspace automation: it's free, runs inside Google, and the URL export gives you a decent-looking PDF fast. The API approach is worth the setup when design control matters — reports that go to customers, invoices that need a logo, documents that represent your brand.
Apps Script permissions
When you run the script for the first time, Google will ask you to authorize:
- See and manage your spreadsheets — to read the sheet and generate the export URL
- Send email as you — if you use GmailApp
- See and manage files in Drive — if you save to Drive
These permissions stay within your Google account. No data leaves Google unless you explicitly call an external API.
Common issues
Export URL returns a login page instead of a PDF
The Authorization: Bearer header with ScriptApp.getOAuthToken() is required. Without it, the export URL redirects to the sign-in page.
Large sheets produce partial PDFs
The built-in export has a page limit. For very large sheets, use the range parameter to export in sections, or fetch the data and render it with a PDF API that handles pagination via HTML flow.
Charts are not included in the export Charts embedded in Google Sheets are sometimes excluded from the URL-based export. If you need charts in the PDF, screenshot the chart with the Sheets API or render a chart in your HTML template using a library like Chart.js.
Font doesn't match my brand The built-in export uses Google's default rendering. To use your own fonts, fetch the data and render it with an HTML template where you control the CSS.
Free tools mentioned:
Start generating PDFs
Build PDF templates with a visual editor. Render them via API from any language in ~300ms.