This workflow corresponds to n8n.io template #18110 — we link there as the canonical source.
This workflow follows the Chainllm → Form Trigger recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "70fxsJSffx58O2g7",
"meta": {
"templateId": "12014",
"templateCredsSetupCompleted": true
},
"name": "Generate a tailored resume from a job posting in Google Docs",
"tags": [],
"nodes": [
{
"id": "b6d55aae-9b45-48b9-ab53-3a22be3c2d27",
"name": "Sticky Note - LinkedIn Warning",
"type": "n8n-nodes-base.stickyNote",
"position": [
480,
1328
],
"parameters": {
"color": 3,
"width": 300,
"height": 242,
"content": "### Heads up\nLinkedIn often requires login to view full postings and may block anonymous scraping. if this returns a login page instead of the job description, use a company careers-page URL or Indeed instead.\n\nIf that still does not work, copy and paste the text inside job_text on the Extract Job Post Text node"
},
"typeVersion": 1
},
{
"id": "2f29e2ac-0cfc-4e4a-a55e-1425c8043af9",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-560,
832
],
"parameters": {
"width": 480,
"height": 896,
"content": "## Generate a tailored resume from a job posting in Google Docs\n\n### How it works\n\nThis workflow takes a job posting URL from a form, adds the candidate\u2019s base profile details, and fetches the posting page. It extracts the job text and uses OpenRouter-powered AI steps to identify requirements, rewrite the resume, and draft a tailored cover letter. The generated resume and cover letter are combined into a new Google Doc, populated with the text, and moved into Google Drive.\n\n### Setup steps\n\n- Configure the form trigger so it captures the job posting URL field expected by the HTTP request node.\n- Update the candidate profile/base resume Set node with the applicant\u2019s name, contact details, portfolio URL, and resume source content.\n- Add valid OpenRouter credentials for the extraction and writing language model nodes, and choose the desired models.\n- Configure Google Docs and Google Drive credentials with permission to create, edit, and move documents.\n- Set the destination Google Drive folder and verify the Google Docs nodes create and insert content into the intended document.\n\n### Customization\n\nAdjust the extraction prompt/schema to capture the job attributes you care about, such as required skills, seniority, keywords, company values, or salary details. You can also modify the resume and cover letter prompts to match a preferred tone, length, formatting style, or target role type."
},
"typeVersion": 1
},
{
"id": "b30fe04a-f8b6-43a2-b35e-57c90620aa96",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
976
],
"parameters": {
"color": 7,
"width": 432,
"height": 320,
"content": "## Collect candidate input\n\nStarts the workflow from a submitted job posting URL and enriches the run with the candidate\u2019s profile and base resume details."
},
"typeVersion": 1
},
{
"id": "b6f21253-a594-418a-8654-dbae397fd078",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
480,
976
],
"parameters": {
"color": 7,
"width": 432,
"height": 320,
"content": "## Fetch posting text\n\nRetrieves the job posting web page and parses the HTML so the workflow can work with the posting\u2019s readable text."
},
"typeVersion": 1
},
{
"id": "7baf2f2a-fc0a-44b7-a20b-8fe29ea5cdfa",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
960,
960
],
"parameters": {
"color": 7,
"width": 352,
"height": 528,
"content": "## Extract job requirements\n\nUses an information extraction step and its OpenRouter model to turn the parsed posting into structured job requirements for downstream generation."
},
"typeVersion": 1
},
{
"id": "53312e06-a61f-4cf5-b76c-0b121e7cde0c",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1344,
832
],
"parameters": {
"color": 7,
"width": 608,
"height": 672,
"content": "## Generate tailored materials\n\nUses AI writing chains to rewrite the candidate\u2019s resume for the extracted requirements and draft a matching cover letter, sharing the nearby OpenRouter writing model."
},
"typeVersion": 1
},
{
"id": "5e28fc34-f15a-4171-b435-aeeaa6f2fb19",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1984,
1008
],
"parameters": {
"color": 7,
"width": 736,
"height": 304,
"content": "## Create application document\n\nCombines the resume and cover letter outputs, creates a Google Doc, inserts the generated text, and moves the finished file in Google Drive."
},
"typeVersion": 1
},
{
"id": "3c46cc41-b68f-4a03-8265-2a05b71ac01a",
"name": "When Form Submitted",
"type": "n8n-nodes-base.formTrigger",
"position": [
48,
1136
],
"parameters": {
"options": {
"buttonLabel": "Tailor my application"
},
"formTitle": "Tailor my Resume & Cover Letter",
"formFields": {
"values": [
{
"fieldLabel": "Job posting URL",
"placeholder": "https://www.linkedin.com/jobs/view/...",
"requiredField": true
},
{
"fieldLabel": "Anything else the AI should know? (optional)",
"placeholder": "e.g. emphasize my leadership experience, I'm open to relocating, etc."
}
]
},
"formDescription": "Paste the URL of a job posting (LinkedIn, Indeed, a company's careers page, etc.) and I'll tailor your resume and cover letter to it."
},
"typeVersion": 2.2
},
{
"id": "fdbbd45a-6e2f-44f2-9203-5b46793036cf",
"name": "Prepare Candidate Profile",
"type": "n8n-nodes-base.set",
"notes": "Fill these in once. base_resume_text is your full resume (copy/paste from RESUME.pdf). This is the only node you need to edit to keep your info current.",
"position": [
288,
1136
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "a1",
"name": "full_name",
"type": "string",
"value": "Mary Jo"
},
{
"id": "a2",
"name": "email",
"type": "string",
"value": "user@example.com"
},
{
"id": "a3",
"name": "phone",
"type": "string",
"value": "+1234567890"
},
{
"id": "a5",
"name": "portfolio_url",
"type": "string",
"value": "https://mary.jo/"
},
{
"id": "a6",
"name": "tone",
"type": "string",
"value": "confident, concise, results-driven, no clich\u00e9s or filler adjectives"
},
{
"id": "a7",
"name": "writing_language",
"type": "string",
"value": "English (US)"
},
{
"id": "a8",
"name": "base_resume_text",
"type": "string",
"value": "=I'm a low-code and AI Product Builder...\n \n \n "
}
]
}
},
"typeVersion": 3.4
},
{
"id": "22422e4c-b4f4-4c4d-914a-80c9ec0eeca4",
"name": "Fetch Job Posting",
"type": "n8n-nodes-base.httpRequest",
"notes": "Some sites (LinkedIn especially) block anonymous scraping or require login and may return a login wall instead of the job text. If extraction comes back empty, paste the job description text directly into a modified version of this workflow instead of a URL.",
"position": [
528,
1136
],
"parameters": {
"url": "={{ $('When Form Submitted').item.json['Job posting URL'] }}",
"options": {
"redirect": {
"redirect": {}
},
"response": {
"response": {
"responseFormat": "text"
}
}
}
},
"typeVersion": 4.2
},
{
"id": "ed2495cd-4437-42c9-86a1-55835ef0800f",
"name": "Extract Job Description",
"type": "n8n-nodes-base.html",
"position": [
768,
1136
],
"parameters": {
"options": {
"trimValues": true,
"cleanUpText": true
},
"operation": "extractHtmlContent",
"extractionValues": {
"values": [
{
"key": "job_text",
"cssSelector": "body"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "13bddb3b-9dbb-48dd-8cfb-570f7b700663",
"name": "AI Content Extractor",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"notes": "Verify exact node type/params against your n8n instance (get_node_types) once MCP access is authorized \u2014 OpenRouter node params can drift between n8n versions.",
"position": [
1024,
1344
],
"parameters": {
"model": "anthropic/claude-sonnet-5",
"options": {
"temperature": 0.2
}
},
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "508905a9-d9f0-4714-afdc-77cf0a32ddfa",
"name": "Extract Requirements",
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"position": [
1008,
1136
],
"parameters": {
"text": "={{ $json.job_text }}",
"options": {},
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"job_title\": { \"type\": \"string\" },\n \"company_name\": { \"type\": \"string\" },\n \"location\": { \"type\": \"string\" },\n \"seniority_level\": { \"type\": \"string\" },\n \"employment_type\": { \"type\": \"string\" },\n \"required_skills\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } },\n \"preferred_skills\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } },\n \"key_responsibilities\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } },\n \"ats_keywords\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } },\n \"company_values_or_culture_notes\": { \"type\": \"string\" }\n },\n \"required\": [\"job_title\", \"company_name\", \"required_skills\", \"key_responsibilities\", \"ats_keywords\"]\n}"
},
"typeVersion": 1.2
},
{
"id": "5e4c9db2-0d07-47a7-97eb-e55bc170b15c",
"name": "AI Writing Assistant",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
1392,
1376
],
"parameters": {
"model": "anthropic/claude-sonnet-5",
"options": {
"temperature": 0.6
}
},
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "6664947a-4549-4ff1-943f-db22b428d03b",
"name": "Revise Resume for Job",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
1648,
992
],
"parameters": {
"text": "=CANDIDATE'S BASE RESUME:\n{{ $('Prepare Candidate Profile').item.json.base_resume_text }}\n\nTARGET JOB:\nTitle: {{ $json.output.job_title }}\nCompany: {{ $json.output.company_name }}\nRequired skills: {{ $json.output.required_skills.join(', ') }}\nPreferred skills: {{ $json.output.preferred_skills.join(', ') }}\nKey responsibilities: {{ $json.output.key_responsibilities.join('; ') }}\nATS keywords to weave in naturally: {{ $json.output.ats_keywords.join(', ') }}\n\nEXTRA CONTEXT FROM CANDIDATE: {{ $('When Form Submitted').item.json['Anything else the AI should know? (optional)'] }}\n\nRewrite the resume above for this specific job.",
"messages": {
"messageValues": [
{
"message": "=You are an expert resume writer and ATS optimization specialist. Rewrite the candidate's resume so it is precisely tailored to the target job, while staying 100% truthful to their actual experience \u2014 never invent employers, titles, dates, or accomplishments.\n\nTone: {{ $('Prepare Candidate Profile').item.json.tone }}\nLanguage: {{ $('Prepare Candidate Profile').item.json.writing_language }}\n\nRules:\n- Reorder and re-emphasize existing bullet points so the most relevant experience leads.\n- Rewrite bullets to mirror the job's language and naturally include ATS keywords, without keyword-stuffing.\n- Keep it to one page worth of content (roughly 500-650 words).\n- Output clean plain text with clear section headers (SUMMARY, EXPERIENCE, SKILLS, EDUCATION), ready to paste into a document. No markdown symbols like ** or #."
}
]
},
"promptType": "define"
},
"retryOnFail": true,
"typeVersion": 1.6,
"waitBetweenTries": 5000
},
{
"id": "4004bb8b-75f0-46c9-bf12-8a057c3ff2f1",
"name": "Write Custom Cover Letter",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
1648,
1280
],
"parameters": {
"text": "=CANDIDATE: {{ $('Prepare Candidate Profile').item.json.full_name }}\n\nCANDIDATE'S BASE RESUME (for real accomplishments to reference):\n{{ $('Prepare Candidate Profile').item.json.base_resume_text }}\n\nTARGET JOB:\nTitle: {{ $json.output.job_title }}\nCompany: {{ $json.output.company_name }}\nKey responsibilities: {{ $json.output.key_responsibilities.join('; ') }}\nCompany values / culture notes: {{ $json.output.company_values_or_culture_notes }}\n\nEXTRA CONTEXT FROM CANDIDATE: {{ $('When Form Submitted').item.json['Anything else the AI should know? (optional)'] }}\n\nWrite the cover letter for this specific job.",
"messages": {
"messageValues": [
{
"message": "=You are an expert career coach writing a compelling, specific, non-generic cover letter. Never use clich\u00e9s like \"I am excited to apply\" or \"I believe I would be a great fit\" \u2014 open with a concrete hook tied to the company or role.\n\nTone: {{ $('Prepare Candidate Profile').item.json.tone }}\nLanguage: {{ $('Prepare Candidate Profile').item.json.writing_language }}\n\nRules:\n- 3-4 short paragraphs, under 350 words total.\n- Paragraph 1: a specific hook \u2014 why this role/company, referencing something real about the company or role, not generic flattery.\n- Paragraph 2-3: connect 2-3 real accomplishments from the resume directly to the job's key responsibilities, with concrete outcomes/numbers where the resume has them.\n- Closing paragraph: confident call to action.\n- Sign off with the candidate's name. Output clean plain text, no markdown symbols."
}
]
},
"promptType": "define"
},
"retryOnFail": true,
"typeVersion": 1.6,
"waitBetweenTries": 5000
},
{
"id": "315a303d-aafb-4a73-abc2-6d9b9a2aee29",
"name": "Merge Resume and Letter",
"type": "n8n-nodes-base.merge",
"position": [
2032,
1136
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2
},
{
"id": "71630973-78e7-468d-b83d-8225945b0926",
"name": "Generate Application Document",
"type": "n8n-nodes-base.googleDocs",
"notes": "folderId is set to Google Drive's 'root' alias (top level of My Drive). Open this node after import and pick a different folder from the dropdown if you want it saved elsewhere \u2014 the MCP can't resolve your real folder list without your live credential.",
"position": [
2224,
1136
],
"parameters": {
"title": "={{ $('Extract Requirements').item.json.output.job_title }} at {{ $('Extract Requirements').item.json.output.company_name }} \u2014 Resume & Cover Letter - {{ $now }}"
},
"credentials": {
"googleDocsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "33d7ec69-23be-49d1-8fd1-671aa6bd28ea",
"name": "Insert Resume and Letter",
"type": "n8n-nodes-base.googleDocs",
"position": [
2400,
1136
],
"parameters": {
"actionsUi": {
"actionFields": [
{
"text": "=TAILORED RESUME\n================\n\n{{ $('Revise Resume for Job').item.json.text }}\n\n\nCOVER LETTER\n============\n\n{{ $('Write Custom Cover Letter').item.json.text }}",
"action": "insert"
}
]
},
"operation": "update",
"documentURL": "={{ $json.id }}"
},
"credentials": {
"googleDocsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "6c142c93-84e5-4b86-ac0f-8e512824a231",
"name": "Move to Drive Folder",
"type": "n8n-nodes-base.googleDrive",
"position": [
2576,
1136
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "id",
"value": "={{ $json.documentId }}"
},
"driveId": {
"__rl": true,
"mode": "list",
"value": "",
"cachedResultUrl": "",
"cachedResultName": ""
},
"folderId": {
"__rl": true,
"mode": "url",
"value": "https://drive.google.com/drive/folders/"
},
"operation": "move"
},
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 3
}
],
"active": false,
"settings": {
"timezone": "Europe/Madrid",
"binaryMode": "separate",
"callerPolicy": "workflowsFromSameOwner",
"errorWorkflow": "tKU8rgsN4cH2Mv9F",
"timeSavedMode": "fixed",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "50698506-5fba-4c06-9640-238457b0f0a8",
"nodeGroups": [],
"connections": {
"Fetch Job Posting": {
"main": [
[
{
"node": "Extract Job Description",
"type": "main",
"index": 0
}
]
]
},
"When Form Submitted": {
"main": [
[
{
"node": "Prepare Candidate Profile",
"type": "main",
"index": 0
}
]
]
},
"AI Content Extractor": {
"ai_languageModel": [
[
{
"node": "Extract Requirements",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"AI Writing Assistant": {
"ai_languageModel": [
[
{
"node": "Revise Resume for Job",
"type": "ai_languageModel",
"index": 0
},
{
"node": "Write Custom Cover Letter",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Extract Requirements": {
"main": [
[
{
"node": "Revise Resume for Job",
"type": "main",
"index": 0
},
{
"node": "Write Custom Cover Letter",
"type": "main",
"index": 0
}
]
]
},
"Revise Resume for Job": {
"main": [
[
{
"node": "Merge Resume and Letter",
"type": "main",
"index": 0
}
]
]
},
"Extract Job Description": {
"main": [
[
{
"node": "Extract Requirements",
"type": "main",
"index": 0
}
]
]
},
"Merge Resume and Letter": {
"main": [
[
{
"node": "Generate Application Document",
"type": "main",
"index": 0
}
]
]
},
"Insert Resume and Letter": {
"main": [
[
{
"node": "Move to Drive Folder",
"type": "main",
"index": 0
}
]
]
},
"Prepare Candidate Profile": {
"main": [
[
{
"node": "Fetch Job Posting",
"type": "main",
"index": 0
}
]
]
},
"Write Custom Cover Letter": {
"main": [
[
{
"node": "Merge Resume and Letter",
"type": "main",
"index": 1
}
]
]
},
"Generate Application Document": {
"main": [
[
{
"node": "Insert Resume and Letter",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleDocsOAuth2ApigoogleDriveOAuth2ApiopenRouterApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow collects a job posting URL via an n8n form, extracts key requirements with OpenRouter, then rewrites your resume and drafts a tailored cover letter and saves both into a new Google Doc (optionally moved to a chosen Google Drive folder). Receives a job posting URL…
Source: https://n8n.io/workflows/18110/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Accounting teams spend hours manually entering purchase bills into accounting systems—copying vendor details, creating items, checking duplicates, and reconciling totals. This workflow removes that ma
Spot Workplace Discrimination Patterns with AI. Uses manualTrigger, lmChatOpenAi, httpRequest, html. Event-driven trigger; 38 nodes.
Spot Workplace Discrimination Patterns with AI. Uses manualTrigger, lmChatOpenAi, httpRequest, html. Event-driven trigger; 38 nodes.
This template can be used to find the content gaps in your competitors' discourse: identifying the topics they are not yet connecting and giving you an opportunity to fill in this gap with your conten
The Recap AI - Nano Banana Static Ad Spinner. Uses formTrigger, @mendable/n8n-nodes-firecrawl, chainLlm, lmChatGoogleGemini. Event-driven trigger; 31 nodes.