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
Before importing the JSON workflow
Important: The JSON workflow blueprint uses the PDF.co community node. Install the PDF.co node in n8n before importing the blueprint. Otherwise, the PDF.co step may be missing when the workflow is imported.
In n8n version 1.94.0 or later:
- Open the Nodes panel.
- Search for PDF.co.
- Select the PDF.co node and click Install Node.
Alternatively, go to Settings → Community Nodes, search for n8n-nodes-pdfco, and install it.
If you already imported the workflow and the PDF.co node is missing, install the node and then import the JSON blueprint again. Node installation may require workspace-owner or instance-owner access.
Option A: I Want It Working Now
- Install the PDF.co community node using the instructions above.
- Download the workflow JSON and import it into 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


