Auto-Fill & Flatten PDF Forms from Google Form → Google Sheets → PDF.co (n8n)
What you’ll have when done
A no-touch pipeline that:
- Listens for new Google Form submissions (arriving as new rows in Google Sheets).
- Maps fields from Sheets into a PDF form using PDF.co.
- Flattens the filled PDF automatically via the PDF.co Profiles feature:
{ 'FlattenDocument()': [] }(This converts form fields to regular content so they cannot be edited.) - Downloads the filled+flattened PDF as binary.
- Optionally emails the PDF and/or uploads it to Google Drive with a clean filename.
Prerequisites
- PDF.co API Key (add in n8n credentials or use HTTP headers).Get yours here : https://app.pdf.co/
- n8n (cloud or self-hosted).
- Google Sheets prepared as a target for Google Form responses.
- A fillable PDF form. Here is a sample Form you can test with.
- Field names & page indexes for the PDF form (get them via PDF Inspector).
Get your PDF field names (with PDF Inspector)
- Open PDF Inspector: https://app.pdf.co/pdf-inspector
- Upload your fillable PDF.
- Click each field on the page. On the left pane, you’ll see details such as:
fieldName(exact name, case sensitive),pageIndex(starts from 0 on PDF.co).
- Copy these names—you’ll use them in the Fill PDF Form call.
Tip: Save the field list somewhere (Notion/Google Doc). You’ll reference them when building the “fields” array in the API call.
Endpoints we’ll use (PDF.co)
We’ll use the Fill PDF Form endpoint (and optionally Email Send):
- Fill PDF Form + Flatten
- Method:
POST - Endpoint:
https://api.pdf.co/v1/pdf/edit/add - Purpose: Fill Form fields with values, and flatten the document using Profiles.
- Documentation : https://docs.pdf.co/api-reference/pdf-add#fill-pdf-forms
PDF.co docs label this under “PDF Add”. Your custom node title “Fill a PDF Form” maps to this endpoint.
- Send Email with Attachment (optional)
- Method:
POST - Endpoint: https://api.pdf.co/v1/email/send
- Documentation : https://docs.pdf.co/api-reference/email/send
Quick Start Options
Option A: I Want It Working Now
- Import this workflow template → copy the JSON (Link Here) into a file and import it in n8n.
- Connect Google Sheets (for the trigger) and Google Drive (for upload).
- Add your PDF.co API key in the “PDFco – Fill & Flatten (POST)” HTTP node headers.
- Update the Google Sheets Trigger with your Sheet (the one receiving Google Form responses).
- Update the Google Drive Upload node with your destination folder ID.
- Put your PDF template URL in the HTTP body (
templatePdfUrl). - Map field names from PDF Inspector (fieldName / page).
- (Optional) Enable Email node if you want the filled PDF emailed automatically.
- Test by submitting the Google Form, then Activate the workflow.
Option B : Step-by-Step Build Guide
Step 1: Trigger on new Google Form submissions (arriving as new rows)
Node: Google Sheets Trigger
What it does: Fires when a new row is added to Sheet1.
Settings
- Document: Select your Google Sheet (the one receiving Google Form responses).
- Sheet:
Sheet1(or your actual tab). - Event:
Row Added
Success looks like: When someone submits the Google Form, a new row appears in Sheets and this node runs, passing the row’s columns as JSON.
Step 2: (Optional) Normalize dates coming from the sheet
If your form captures dates as MM/DD/YYYY, convert to YYYY-MM-DD for consistency (good for filenames and records).
Node: Code
What it does: Scans incoming fields and adds an (ISO) version where it finds US-style dates.
Paste this JS in the Code node:
/**
* n8n Code Node
* Converts MM/DD/YYYY → ISO (YYYY-MM-DD)
* - Valid dates: keep original and add "<Field> (ISO)"
* - Invalid dates: remove original, add "<Field> (error)" = "nonexistent-calendar-date"
*/
function parseUsDateStrict(str) {
if (typeof str !== 'string') return { ok: false };
// Match pattern loosely; we'll validate logically afterward
const re = /^(?<m>0?[1-9]|1[0-2])\/(?<d>\d{1,2})\/(?<y>\d{4})$/;
const match = str.match(re);
if (!match) return { ok: false };
const month = parseInt(match.groups.m, 10);
const day = parseInt(match.groups.d, 10);
const year = parseInt(match.groups.y, 10);
// Quick sanity range
if (day < 1 || month < 1 || month > 12 || year < 1000) return { ok: false };
// Check if date actually exists
const dt = new Date(year, month - 1, day);
const isReal =
dt.getFullYear() === year &&
dt.getMonth() === month - 1 &&
dt.getDate() === day;
if (!isReal) return { ok: false };
// Return ISO format
return {
ok: true,
iso: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
};
}
return items.map(item => {
const input = { ...item.json };
const output = {};
for (const key of Object.keys(input)) {
const val = input[key];
const looksLikeDate = typeof val === 'string' && /\d{1,2}\/\d{1,2}\/\d{2,4}/.test(val);
if (looksLikeDate) {
const res = parseUsDateStrict(val);
if (res.ok) {
// Valid date: keep original and ISO
output[key] = val;
output[`${key} (ISO)`] = res.iso;
} else {
// Invalid: remove original, add uniform error
output[`${key} (error)`] = 'nonexistent-calendar-date';
}
} else {
// Pass through non-date fields untouched
output[key] = val;
}
}
return { json: output };
});Success looks like: Your item now includes fields like Created At (ISO) from Created At.
Step 3: Fill the PDF form and flatten it with PDF.co
You can use your custom PDF.co node or a generic HTTP Request (POST). I’ll show both.
Option A: Your custom PDFco node (“Fill a PDF Form”)
Node: PDFco Api (Fill a PDF Form)
What it does: Fills fields and applies Profiles to flatten.
Key parameters (match to your sample):
- Operation: Fill a PDF Form
- Source (url/filetoken): The PDF to fill (filetoken or URL).
- Fields → metadataValues: list of fields with
fieldNamematching the PDF. - Advanced Options → name: output PDF name.
- Advanced Options → profiles: { 'FlattenDocument()': [] }
Example (from your sample, adapted):
// Fields: use exact fieldName from PDF Inspector, and values from the Sheet row
Asignee_Name ← $json["Asignee's Name"]
Created_at ← $json["Created At (ISO)"] or $json["Created At"]
Due_Date ← $json["Due on (ISO)"] or $json["Due on"]
Task ← $json["Task"]
Advanced Options:
name = Assigned Task to {{ $json["Asignee's Name"] }}
profiles = { 'FlattenDocument()': [] }
What flatten does: The Profile FlattenDocument() renders filled form fields into the page content so they cannot be edited later in a PDF viewer.
Success looks like: The node returns JSON with url pointing to the filled + flattened PDF.
Option B: Use HTTP Request (POST) directly
Node: HTTP Request (POST to PDF.co) Headers:
x-api-key: <YOUR_PDFCO_API_KEY>Content-Type: application/json
URL: https://api.pdf.co/v1/pdf/edit/add
Body (JSON): (edit field names and URL/file per your file source)
{
"url": "{{ $json.pdfUrl || 'https://YOUR_STORAGE/form-template.pdf' }}",
"name": "Assigned Task to {{ $json['Asignee\\'s Name'] }}",
"fields": [
{ "fieldName": "Asignee_Name", "pages": "0-", "text": "{{ $json['Asignee\\'s Name'] }}", "fontName": "Arial", "size": 9 },
{ "fieldName": "Created_at", "pages": "0-", "text": "{{ $json['Created At (ISO)'] || $json['Created At'] }}", "fontName": "Arial", "size": 9 },
{ "fieldName": "Due_Date", "pages": "0-", "text": "{{ $json['Due on (ISO)'] || $json['Due on'] }}", "fontName": "Arial", "size": 9 },
{ "fieldName": "Task", "pages": "0-", "text": "{{ $json['Task'] }}", "fontName": "Arial", "size": 9 }
],
"profiles": "{ 'FlattenDocument()': [] }"
}Notes:
pages: "0-"means “all pages from page index 0”. If your field is on a specific page, you can set"pages": "0"etc.- If your template is stored in n8n binary, first upload it to a temp storage (PDF.co
file/uploador your own) and pass the URL here.
Success looks like: JSON with a url to the filled+flattened PDF.
Step 4: Download the result as binary (so Drive can upload it)
Node: HTTP Request (GET)
What it does: Takes the url returned by the fill step and downloads the finished PDF as binary.
Settings
- URL:
={{ $json.url }} - Method:
GET - Options → Add Option → Response Format: File
- Options → Add Option → Binary Property:
data
Success looks like: The execution shows a Binary tab with data.
Step 5: Upload to Google Drive
Node: Google Drive → Upload File
What it does: Saves the finished PDF to your OUTPUT folder.
Settings
- Resource: File
- Operation: Upload
- Input Data Field Name (Binary Property):
data - Parent Drive: My Drive
- Parent Folder by ID: (pick your output folder)
If you want a standardized filename with the date, either set name in the PDF.co call (as we did) or add a Set node before upload to specify name like:
{{ 'Assigned Task to ' + $json["Asignee's Name"] + ' - ' + ($json["Created At (ISO)"] || $json["Created At"]) + '.pdf' }}Step 6: (Optional) Email the PDF via PDF.co
Node: HTTP Request (POST)
URL: https://api.pdf.co/v1/email/send
Headers: x-api-key, Content-Type: application/json
Body (JSON) — Option B (valid JSON, single line, escaped):
{
"url": "{{ $json.url }}",
"from": "your_email@gmail.com",
"to": "recipient@example.com",
"subject": "New Task Assigned: {{ $('Code').item.json.Task }}",
"bodyHtml": "<p>Hi,</p><p>Please find the attached, filled and flattened PDF form.</p><ul><li><strong>Task:</strong> {{ $('Code').item.json.Task }}</li><li><strong>Assignee:</strong> {{ $('Code').item.json['Asignee\\'s Name'] }}</li><li><strong>Created On:</strong> {{ $('Code').item.json['Created At (ISO)'] || $('Code').item.json['Created At'] }}</li><li><strong>Due On:</strong> {{ $('Code').item.json['Due on (ISO)'] || $('Code').item.json['Due on'] }}</li></ul><p>Best regards,<br>Project Automation Team</p>",
"smtpserver": "smtp.gmail.com",
"smtpport": "587",
"smtpusername": "your_email@gmail.com",
"smtppassword": "your_app_password",
"async": false
}Congratulations! You’ve successfully automated PDF form filling and flattening. Your workflow now:
- Captures submissions from Google Forms (via Google Sheets new rows).
- Normalizes dates to YYYY-MM-DD for clean, auditable filenames.
- Fills your PDF template fields with Sheet data using PDF.co (Endpoint:
POST /v1/pdf/edit/add). - Flattens the document automatically with Profiles:
{ 'FlattenDocument()': [] }so fields can’t be edited. - Downloads the finished PDF as binary and uploads it to your chosen Google Drive folder.
- (Optional) Emails the filled & flattened PDF using PDF.co Email (Endpoint:
POST /v1/email/send).
This setup is robust and scalable—from small teams to enterprise workflows. Built something cool with this flow? Share it with us @pdfdotco
Related Tutorials



