Manual document creation is a productivity killer. When you're managing 18 different document templates for rental agreements, sales mandates, and legal forms, choosing the right template and filling in fields manually wastes hours every week. This n8n workflow automates the entire process: a user fills out a form, the system selects the correct template using decision tree logic, populates dynamic fields, applies conditional formatting, and generates clean Word and PDF outputs.
The Problem: Manual Document Generation Doesn't Scale
Real estate professionals, legal teams, and property managers face the same bottleneck: creating customized documents from templates. Each client requires specific clauses, conditional sections, and accurate data propagation across multiple documents.
Current challenges:
- Manually selecting the correct template from 18 options based on complex criteria
- Copy-pasting client data into multiple fields across documents
- Remembering which sections to show or hide based on specific conditions
- Ensuring consistent formatting, fonts, and layouts across all outputs
- Converting documents to multiple formats (Google Docs, Word, PDF)
Business impact:
- Time spent: 2-4 hours per week per team member on document preparation
- Error rate: 15-20% of documents require corrections due to wrong template selection or missing fields
- Client delays: 24-48 hour turnaround for document delivery
Existing tools like Make.com can handle this, but you lose control over customization and pay per operation. Building this in n8n gives you unlimited executions and complete flexibility.
The Solution Overview
This n8n workflow transforms form submissions into fully formatted documents through intelligent template selection and dynamic population. When a user submits a form (Typeform, Google Forms, or Airtable), the workflow evaluates their responses against a decision tree, selects the appropriate template from your library, populates all dynamic fields, applies conditional logic to show/hide sections, and generates outputs in Google Docs, Word, and PDF formats. The system handles 18 different document types including rental mandates, lease agreements, sales contracts, and legal annexes—all with consistent formatting and zero manual intervention.
What You'll Build
This workflow delivers a complete document automation system that handles complex business logic and multi-format output generation.
| Component | Technology | Purpose |
|---|---|---|
| Form Input | Typeform/Google Forms/Airtable | Capture user responses and trigger workflow |
| Decision Engine | n8n Function Nodes + Switch Nodes | Evaluate conditions and select correct template |
| Template Storage | Google Drive/Dropbox | Store 18 master document templates |
| Document Generation | Google Docs API + Pandoc | Populate fields and apply conditional logic |
| Format Conversion | Pandoc/CloudConvert API | Convert to Word (.docx) and PDF |
| Output Delivery | Email/Google Drive | Deliver final documents to users |
Key capabilities:
- Automatic template selection from 18 options based on decision tree logic
- Dynamic field population with data propagation across documents
- Conditional section visibility (show/hide paragraphs based on responses)
- Text variations and optional clauses triggered by specific conditions
- Multi-format output (Google Docs, Word, PDF) with consistent formatting
- Automatic logo placement, font styling, and margin control
Prerequisites
Before starting, ensure you have:
- n8n instance (cloud or self-hosted version 1.0+)
- Google account with Drive and Docs API enabled
- Form platform account (Typeform, Google Forms, or Airtable)
- CloudConvert API key (free tier: 25 conversions/day) or Pandoc installed locally
- 18 document templates prepared with placeholder syntax (e.g.,
{{client_name}}) - Basic JavaScript knowledge for decision tree logic in Function nodes
Step 1: Set Up Form Trigger and Data Capture
The workflow begins when a user submits a form. You'll configure a webhook or direct integration to capture responses in real-time.
Configure the Trigger Node
- Add a Webhook node or Typeform Trigger node to your canvas
- For Webhook: Set method to POST and generate a unique webhook URL
- For Typeform: Authenticate with your Typeform account and select your form
- Test the trigger by submitting a sample form response
Node configuration for Webhook:
{
"httpMethod": "POST",
"path": "document-automation",
"responseMode": "onReceived",
"options": {}
}
Why this works:
Webhooks provide instant triggering (sub-second latency) compared to polling-based triggers that check every 5-15 minutes. For document generation, users expect immediate processing.
Extract and structure form data
- Add a Function node after your trigger
- Parse the incoming JSON to extract all form fields
- Create a standardized data object for downstream nodes
Function node code:
// Extract form responses
const formData = $input.item.json;
// Structure data for template population
return {
json: {
client_name: formData.client_name || '',
property_type: formData.property_type || '',
lease_type: formData.lease_type || '',
duration: formData.duration || '',
rental_amount: formData.rental_amount || '',
deposit_required: formData.deposit_required || false,
furnished: formData.furnished || false,
// Add all 30-40 fields from your form
timestamp: new Date().toISOString()
}
};
Variables to customize:
- Field names must match your form's output structure exactly
- Add default values for optional fields to prevent undefined errors
Step 2: Implement Decision Tree Logic for Template Selection
This is the brain of your workflow. Based on form responses, you'll navigate a decision tree to select the correct template from 18 options.
Build the decision tree structure
- Add a Function node named "Decision Tree Logic"
- Implement nested conditional logic that mirrors your business rules
- Output a
template_idthat maps to your template library
Decision tree implementation:
const data = $input.item.json;
let template_id = '';
// Primary decision: Document category
if (data.document_category === 'rental') {
// Secondary decision: Lease type
if (data.lease_type === 'residential') {
if (data.furnished === true) {
template_id = 'rental_residential_furnished';
} else {
template_id = 'rental_residential_unfurnished';
}
} else if (data.lease_type === 'commercial') {
template_id = 'rental_commercial';
} else if (data.lease_type === 'seasonal') {
template_id = 'rental_seasonal';
}
} else if (data.document_category === 'sales') {
if (data.property_type === 'apartment') {
template_id = 'sales_mandate_apartment';
} else if (data.property_type === 'house') {
template_id = 'sales_mandate_house';
}
} else if (data.document_category === 'legal') {
// Map to specific legal document types
template_id = `legal_${data.legal_document_type}`;
}
return {
json: {
...data,
template_id: template_id,
template_path: `/templates/${template_id}.docx`
}
};
Why this approach:
Nested conditionals in a Function node execute in microseconds and give you complete control over complex logic. Switch nodes work for simple branching, but decision trees with 5+ levels become unwieldy with visual nodes. This JavaScript approach scales to hundreds of conditions.
Add a validation check
- Add a Switch node after the decision tree
- Route to an error handler if
template_idis empty - Send an alert email for unmapped scenarios
Step 3: Retrieve Template and Populate Dynamic Fields
Once you've selected the correct template, you'll fetch it from storage and replace all placeholders with actual data.
Fetch the template file
- Add a Google Drive node set to "Download File"
- Use the
template_pathfrom the previous step - Configure to output as binary data
Google Drive node configuration:
{
"operation": "download",
"fileId": "={{ $json.template_id }}",
"options": {
"googleFileConversion": {
"conversion": {
"docsToFormat": "docx"
}
}
}
}
Replace placeholders with form data
- Add a Function node to process the document
- Use a library like
docxtemplater(if self-hosted) or string replacement for simple cases - Handle all dynamic fields from your form
Template population logic:
const data = $input.item.json;
const binaryData = $input.item.binary.data;
// For simple placeholder replacement
let documentContent = binaryData.toString('utf8');
// Replace all placeholders
documentContent = documentContent
.replace(/{{client_name}}/g, data.client_name)
.replace(/{{property_address}}/g, data.property_address)
.replace(/{{rental_amount}}/g, data.rental_amount)
.replace(/{{lease_start_date}}/g, data.lease_start_date);
// Continue for all 40+ fields
return {
json: data,
binary: {
data: Buffer.from(documentContent, 'utf8')
}
};
For advanced conditional sections:
// Remove entire paragraphs if conditions aren't met
if (data.deposit_required === false) {
documentContent = documentContent.replace(/{{#if_deposit}}[\s\S]*?{{\/if_deposit}}/g, '');
}
// Add optional clauses
if (data.pets_allowed === true) {
const petClause = "The tenant is authorized to keep domestic pets...";
documentContent = documentContent.replace(/{{optional_pet_clause}}/g, petClause);
} else {
documentContent = documentContent.replace(/{{optional_pet_clause}}/g, '');
}
Why this works:
Binary data manipulation in n8n preserves document formatting. You're not converting to plain text and losing styles—you're performing surgical replacements within the document structure.
Step 4: Apply Conditional Formatting and Section Visibility
Beyond simple field replacement, you need to show/hide entire sections and apply text variations based on complex conditions.
Implement conditional block logic
- Use marker syntax in your templates:
{{#if_condition}}...{{/if_condition}} - Add a Function node to evaluate conditions and remove blocks
- Handle nested conditions for complex scenarios
Conditional section removal:
const data = $input.item.json;
let content = $input.item.binary.data.toString('utf8');
// Define condition evaluators
const conditions = {
furnished: data.furnished === true,
deposit_required: data.deposit_required === true,
commercial_lease: data.lease_type === 'commercial',
co_ownership: data.property_type === 'apartment' && data.co_ownership === true
};
// Remove sections where conditions are false
Object.keys(conditions).forEach(key => {
if (!conditions[key]) {
const regex = new RegExp(`{{#if_${key}}}[\\s\\S]*?{{/if_${key}}}`, 'g');
content = content.replace(regex, '');
} else {
// Remove the markers but keep the content
content = content.replace(new RegExp(`{{#if_${key}}}`, 'g'), '');
content = content.replace(new RegExp(`{{/if_${key}}}`, 'g'), '');
}
});
return {
json: data,
binary: { data: Buffer.from(content, 'utf8') }
};
Text variation logic:
// Apply small text changes based on conditions
if (data.lease_duration === '12_months') {
content = content.replace(/{{duration_text}}/g, 'une durée de douze (12) mois');
} else if (data.lease_duration === '6_months') {
content = content.replace(/{{duration_text}}/g, 'une durée de six (6) mois');
}
Step 5: Generate Multi-Format Outputs
Users need documents in Google Docs, Word, and PDF formats. You'll convert the populated template to all three formats with consistent styling.
Create Google Docs version
- Add a Google Docs node set to "Create Document from Text"
- Upload your populated content
- Apply formatting rules (fonts, margins, colors)
Convert to Word (.docx)
- Add an HTTP Request node to CloudConvert API
- Set endpoint to
https://api.cloudconvert.com/v2/convert - Pass your document for conversion
CloudConvert API configuration:
{
"method": "POST",
"url": "https://api.cloudconvert.com/v2/jobs",
"authentication": "headerAuth",
"headerAuth": {
"name": "Authorization",
"value": "Bearer YOUR_API_KEY"
},
"body": {
"tasks": {
"import-file": {
"operation": "import/upload"
},
"convert-file": {
"operation": "convert",
"input": "import-file",
"output_format": "docx"
},
"export-file": {
"operation": "export/url",
"input": "convert-file"
}
}
}
}
Generate PDF version
- Add another HTTP Request node to CloudConvert
- Set
output_formatto "pdf" - Configure PDF options (page size: A4, margins: 2.5cm)
Why this approach:
CloudConvert maintains document fidelity better than Google's native conversion. Fonts, spacing, and complex layouts remain intact. The API handles 25 conversions/day on the free tier—sufficient for most small teams.
Workflow Architecture Overview
This workflow consists of 12 nodes organized into 4 main sections:
- Data ingestion (Nodes 1-3): Webhook trigger captures form submission, Function node structures data, validation checks for required fields
- Template selection (Nodes 4-6): Decision tree logic evaluates conditions, Switch node routes to error handler if needed, template ID mapped to file path
- Document generation (Nodes 7-9): Google Drive fetches template, Function nodes populate fields and apply conditional logic, formatting rules applied
- Output delivery (Nodes 10-12): CloudConvert generates Word and PDF versions, files uploaded to Google Drive, email notification sent with download links
Execution flow:
- Trigger: Webhook receives POST request from form submission
- Average run time: 8-12 seconds for complete document generation
- Key dependencies: Google Drive API, CloudConvert API, email service (SMTP or SendGrid)
Critical nodes:
- Function (Decision Tree): Evaluates 15-20 conditions to select correct template from 18 options
- Function (Template Population): Replaces 40+ placeholders and removes conditional sections
- HTTP Request (CloudConvert): Converts to Word and PDF while preserving formatting
The complete n8n workflow JSON template is available at the bottom of this article.
Critical Configuration Settings
Google Drive Integration
Required fields:
- Service Account JSON key (for server-to-server authentication)
- Folder ID where templates are stored
- Permissions: Read access to template folder, Write access to output folder
Common issues:
- Using personal OAuth instead of Service Account → Workflow breaks when your session expires
- Always use Service Accounts for production automations
CloudConvert API
Required fields:
- API Key from cloudconvert.com/dashboard/api/v2/keys
- Sandbox mode: false (for production)
- Webhook URL: Your n8n webhook for conversion completion notifications
Rate limits:
- Free tier: 25 conversions/day
- Paid tier: Starts at $9/month for 500 conversions
- Implement caching for frequently generated documents to stay within limits
Template placeholder syntax
Use this exact format in your Word templates:
{{field_name}} - Simple replacement
{{#if_condition}}...{{/if_condition}} - Conditional sections
{{#each items}}{{item_name}}{{/each}} - Loops (for lists)
Why this approach:
Double curly braces are recognized by most templating engines and won't be accidentally triggered by regular document text. They're also easy to find/replace programmatically.
Testing & Validation
Test each template individually
- Create a test form submission for each of the 18 document types
- Verify correct template selection by checking the
template_idoutput - Review generated documents for:
- All placeholders replaced (no
{{field}}remaining) - Conditional sections showing/hiding correctly
- Formatting preserved (fonts, margins, logos)
- No empty lines where sections were removed
- All placeholders replaced (no
Common issues and fixes:
| Issue | Cause | Solution |
|---|---|---|
| Placeholders not replaced | Field name mismatch | Check form output field names match template placeholders exactly |
| Formatting lost in PDF | Wrong conversion settings | Set CloudConvert to preserve styles: "preserve_formatting": true |
| Wrong template selected | Decision tree logic error | Add logging to Function node: console.log('Selected template:', template_id) |
| Slow execution (>30 sec) | Sequential API calls | Use n8n's batch processing to convert multiple formats in parallel |
Run end-to-end tests
- Submit forms that trigger each branch of your decision tree
- Verify outputs in all three formats (Google Docs, Word, PDF)
- Check email delivery and attachment sizes
- Test error handling by submitting invalid data
Production Deployment Checklist
| Area | Requirement | Why It Matters |
|---|---|---|
| Error Handling | Add Error Trigger node with retry logic (3 attempts, exponential backoff) | Prevents data loss when APIs are temporarily unavailable |
| Monitoring | Set up workflow execution logs and email alerts for failures | Detect issues within minutes instead of discovering them when clients complain |
| Credentials | Use environment variables for all API keys, never hardcode | Security best practice—prevents credential leaks in exported workflows |
| Rate Limiting | Implement queue system if processing >25 docs/day | Avoids hitting CloudConvert limits and failed conversions |
| Backup | Daily export of workflow JSON to Git repository | Enables version control and quick rollback if changes break production |
| Documentation | Add sticky notes to canvas explaining each section's purpose | Reduces modification time from 2-4 hours to 30 minutes for future updates |
Real-World Use Cases
Use Case 1: Real Estate Agency Document Automation
- Industry: Residential & commercial real estate
- Scale: 50-100 documents per month (rental mandates, leases, sales contracts)
- Modifications needed: Add Airtable integration to store client records, connect to DocuSign for electronic signatures
- ROI: Saves 15 hours/month of administrative work
Use Case 2: Legal Practice Client Intake
- Industry: Law firm specializing in contracts
- Scale: 200+ documents per month across 18 template types
- Modifications needed: Add client portal (Softr or Stacker) for form submission, integrate with practice management software (Clio)
- ROI: Reduces document preparation time from 45 minutes to 2 minutes per client
Use Case 3: Property Management Company
- Industry: Multi-family property management
- Scale: 300+ lease agreements annually
- Modifications needed: Connect to property management system API to auto-populate unit details, add SMS notifications via Twilio
- ROI: Eliminates 95% of manual data entry errors
Customizing This Workflow
Alternative Integrations
Instead of CloudConvert:
- Pandoc (self-hosted): Best for unlimited conversions - requires Docker container with Pandoc installed, add Execute Command node
- Microsoft Graph API: Better if you're in Microsoft 365 ecosystem - native Word/PDF generation, requires Azure app registration
- PDFMonkey: Use when you need pixel-perfect PDF layouts - template-based approach, 1000 docs/month on free tier
Workflow Extensions
Add automated reporting:
- Add a Schedule node to run weekly
- Query your database for all documents generated
- Create summary report with template usage statistics
- Email to management team
- Nodes needed: +6 (Schedule, Database query, Aggregate, Function, Email)
Scale to handle more data:
- Replace single document processing with batch mode
- Add Queue node to process 50 documents at once
- Implement parallel execution for format conversion (Google Docs, Word, PDF generated simultaneously)
- Performance improvement: 3x faster for bulk operations
Integration possibilities:
| Add This | To Get This | Complexity |
|---|---|---|
| DocuSign API | Electronic signature collection | Medium (8 nodes) |
| Airtable sync | Client database with document history | Easy (3 nodes) |
| Slack notifications | Real-time alerts when documents are ready | Easy (2 nodes) |
| Stripe payment | Charge clients before document generation | Medium (6 nodes) |
| Zapier webhook | Connect to 3000+ apps without custom code | Easy (1 node) |
Add multi-language support:
- Add a language selection field to your form
- Create template versions in each language (18 templates × 3 languages = 54 templates)
- Modify decision tree to append language code to
template_id - Example:
rental_residential_furnished_fr,rental_residential_furnished_en
Implement version control:
- Add a version number field to each template
- Store template change history in a database
- Include version number in generated document metadata
- Allows you to track which template version was used for each client
Get Started Today
Ready to automate your document generation?
- Download the template: Scroll to the bottom of this article to copy the n8n workflow JSON
- Import to n8n: Go to Workflows → Import from URL or File, paste the JSON
- Configure your services: Add your API credentials for Google Drive, CloudConvert, and your form platform
- Prepare your templates: Create 18 Word documents with placeholder syntax and upload to Google Drive
- Test with sample data: Submit test forms for each document type and verify outputs
- Deploy to production: Activate the workflow and connect your live form
Need help customizing this workflow for your specific document types or integrating with your existing systems? Schedule an intro call with Atherial at https://atherial.ai/contact.
n8n Workflow JSON Template
{
"name": "Document Automation - Template Generation",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "document-automation",
"responseMode": "onReceived"
},
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [250, 300],
"webhookId": "auto-generated"
},
{
"parameters": {
"functionCode": "const formData = $input.item.json;
return {
json: {
client_name: formData.client_name || '',
property_type: formData.property_type || '',
lease_type: formData.lease_type || '',
document_category: formData.document_category || '',
furnished: formData.furnished || false,
timestamp: new Date().toISOString()
}
};"
},
"name": "Structure Form Data",
"type": "n8n-nodes-base.function",
"position": [450, 300]
},
{
"parameters": {
"functionCode": "const data = $input.item.json;
let template_id = '';
if (data.document_category === 'rental') {
if (data.lease_type === 'residential') {
template_id = data.furnished ? 'rental_residential_furnished' : 'rental_residential_unfurnished';
} else if (data.lease_type === 'commercial') {
template_id = 'rental_commercial';
}
} else if (data.document_category === 'sales') {
template_id = `sales_mandate_${data.property_type}`;
}
return {
json: {
...data,
template_id: template_id,
template_path: `/templates/${template_id}.docx`
}
};"
},
"name": "Decision Tree Logic",
"type": "n8n-nodes-base.function",
"position": [650, 300]
},
{
"parameters": {
"operation": "download",
"fileId": "={{ $json.template_id }}"
},
"name": "Fetch Template",
"type": "n8n-nodes-base.googleDrive",
"position": [850, 300]
},
{
"parameters": {
"functionCode": "const data = $input.item.json;
const binaryData = $input.item.binary.data;
let content = binaryData.toString('utf8');
content = content
.replace(/{{client_name}}/g, data.client_name)
.replace(/{{property_type}}/g, data.property_type);
return {
json: data,
binary: { data: Buffer.from(content, 'utf8') }
};"
},
"name": "Populate Template",
"type": "n8n-nodes-base.function",
"position": [1050, 300]
},
{
"parameters": {
"url": "https://api.cloudconvert.com/v2/jobs",
"authentication": "headerAuth",
"options": {
"bodyContentType": "json"
},
"bodyParametersJson": "={
\"tasks\": {
\"convert-file\": {
\"operation\": \"convert\",
\"output_format\": \"docx\"
}
}
}"
},
"name": "Convert to Word",
"type": "n8n-nodes-base.httpRequest",
"position": [1250, 250]
},
{
"parameters": {
"url": "https://api.cloudconvert.com/v2/jobs",
"authentication": "headerAuth",
"options": {
"bodyContentType": "json"
},
"bodyParametersJson": "={
\"tasks\": {
\"convert-file\": {
\"operation\": \"convert\",
\"output_format\": \"pdf\"
}
}
}"
},
"name": "Convert to PDF",
"type": "n8n-nodes-base.httpRequest",
"position": [1250, 350]
}
],
"connections": {
"Webhook": {
"main": [[{"node": "Structure Form Data", "type": "main", "index": 0}]]
},
"Structure Form Data": {
"main": [[{"node": "Decision Tree Logic", "type": "main", "index": 0}]]
},
"Decision Tree Logic": {
"main": [[{"node": "Fetch Template", "type": "main", "index": 0}]]
},
"Fetch Template": {
"main": [[{"node": "Populate Template", "type": "main", "index": 0}]]
},
"Populate Template": {
"main": [[{"node": "Convert to Word", "type": "main", "index": 0}, {"node": "Convert to PDF", "type": "main", "index": 0}]]
}
}
}
