This workflow corresponds to n8n.io template #17781 — we link there as the canonical source.
This workflow follows the Apifyn8N Nodes Apify → Google Sheets 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 →
{
"name": "Log weekly hiring signals from LinkedIn jobs into Google Sheets",
"nodes": [
{
"id": "e0b3c427-0001-4f01-a001-000000000001",
"name": "Every Monday morning",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
0,
520
],
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtDay": [
1
],
"triggerAtHour": 8
}
]
}
},
"typeVersion": 1.2
},
{
"id": "e0b3c427-0002-4f02-a002-000000000002",
"name": "Set your hiring signal watch",
"type": "n8n-nodes-base.set",
"position": [
260,
520
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "e0b3c427-0011-4f11-a011-+1234567890",
"name": "roles",
"type": "array",
"value": "[\"head of revenue operations\", \"director of demand generation\"]"
},
{
"id": "e0b3c427-0012-4f12-a012-+1234567890",
"name": "location",
"type": "string",
"value": "United States"
},
{
"id": "e0b3c427-0013-4f13-a013-+1234567890",
"name": "company",
"type": "string",
"value": ""
},
{
"id": "e0b3c427-0014-4f14-a014-+1234567890",
"name": "lookbackDays",
"type": "number",
"value": "30"
},
{
"id": "e0b3c427-0015-4f15-a015-+1234567890",
"name": "maxJobs",
"type": "number",
"value": "25"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "e0b3c427-0003-4f03-a003-000000000003",
"name": "One search per role",
"type": "n8n-nodes-base.splitOut",
"position": [
520,
520
],
"parameters": {
"include": "allOtherFields",
"options": {},
"fieldToSplitOut": "roles"
},
"typeVersion": 1
},
{
"id": "e0b3c427-0004-4f04-a004-000000000004",
"name": "Search LinkedIn jobs",
"type": "@apify/n8n-nodes-apify.apify",
"position": [
780,
520
],
"parameters": {
"memory": 128,
"actorId": "johnvc~linkedin-jobs-api",
"resource": "Actors",
"operation": "Run actor and get dataset",
"customBody": "={{ JSON.stringify({ keyword: $json.roles, location: $json.location, company: $json.company || undefined, maxJobs: $json.maxJobs }) }}"
},
"typeVersion": 1
},
{
"id": "e0b3c427-0005-4f05-a005-000000000005",
"name": "Keep rows that are real jobs",
"type": "n8n-nodes-base.filter",
"position": [
1040,
520
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "e0b3c427-0041-4f41-a041-+1234567890",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.result_type }}",
"rightValue": "job"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "e0b3c427-0006-4f06-a006-000000000006",
"name": "Keep recent postings",
"type": "n8n-nodes-base.code",
"position": [
1300,
520
],
"parameters": {
"jsCode": "// Keep only postings inside the lookback window, then shape them for a sheet.\n// The date filter lives here rather than in the search because the Actor's\n// timeRange input is rejected upstream on every value, so the search returns\n// postings of all ages and they have to be trimmed after the fact.\nconst cfg = $('Set your hiring signal watch').first().json;\nconst weekOf = $now.toFormat('yyyy-MM-dd');\nconst maxAgeDays = Number(cfg.lookbackDays) || 30;\n\nconst clip = (s, n) => {\n const v = String(s || '').replace(/\\s+/g, ' ').trim();\n return v.length > n ? v.slice(0, n - 3) + '...' : v;\n};\n\nconst rows = [];\nfor (const item of $input.all()) {\n const j = item.json;\n const posted = j.postedDate ? new Date(j.postedDate) : null;\n if (!posted || isNaN(posted)) continue;\n\n const ageDays = Math.floor(($now.toJSDate() - posted) / 86400000);\n if (ageDays > maxAgeDays || ageDays < 0) continue;\n\n rows.push({\n json: {\n 'Week of': weekOf,\n 'Job title': clip(j.title, 150),\n Company: j.companyName || '',\n Location: j.location || '',\n Posted: String(j.postedDate).slice(0, 10),\n 'Days old': ageDays,\n Seniority: j.seniorityLevel || '',\n 'Employment type': j.employmentType || '',\n 'Job function': clip(j.jobFunction, 120),\n Applicants: typeof j.numApplicants === 'number' ? j.numApplicants : '',\n 'Job link': j.jobUrl || '',\n },\n });\n}\nreturn rows;\n"
},
"typeVersion": 2
},
{
"id": "e0b3c427-0007-4f07-a007-000000000007",
"name": "Append hiring signals to sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
1560,
520
],
"parameters": {
"columns": {
"value": {},
"mappingMode": "autoMapInputData",
"matchingColumns": []
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": ""
},
"documentId": {
"__rl": true,
"mode": "list",
"value": ""
}
},
"typeVersion": 4.5
},
{
"id": "e0b3c427-0090-4f90-a090-000000000090",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-620,
40
],
"parameters": {
"width": 520,
"height": 1240,
"content": "## Log weekly hiring signals from LinkedIn jobs into Google Sheets\n\nNo Bright Data account and no scraping vendor subscription. Every week this searches public LinkedIn job listings for the roles you care about and appends one row per posting to your sheet: title, company, location, when it was posted, how old it is, seniority, and how many people have applied.\n\nA company hiring for a role you sell into is a buying signal with a timestamp on it. Set the company field instead and it becomes an account watch: everything a target company is hiring for, which tells you where their budget went this quarter.\n\n**Who's it for**\nSales teams working hiring as an intent signal, recruiters tracking who is staffing up, and job seekers who would rather have a spreadsheet than an alert email.\n\n**How it works**\n1. A schedule trigger fires once a week\n2. One Set node holds your roles, location, optional company, and lookback window\n3. The Apify node runs one LinkedIn job search per role\n4. A Code node keeps the postings inside your window and shapes them for the sheet\n\n**Setup**\n1. Create a free Apify account and add your API token to the Apify credential\n2. Connect your Google account in the Sheets node and pick a spreadsheet\n3. Replace the example roles in the config node, and set a company if you want to watch one account\n\n**Good to know**\nAbout a cent per job returned, so two roles at 25 jobs each is roughly 20 cents a week.\nSearch results are not sorted by date, so the workflow filters on posting age itself. Widening maxJobs finds more recent postings but costs more.\nThe Applicants column is a useful freshness signal: a posting with very few applicants is usually days old.\nActor: https://apify.com/johnvc/linkedin-jobs-api?fpr=9n7kx3&fp_sid=n8n"
},
"typeVersion": 1
},
{
"id": "e0b3c427-0091-4f91-a091-000000000091",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-40,
60
],
"parameters": {
"color": 7,
"width": 240,
"height": 240,
"content": "### 1. Schedule\nRuns every Monday morning. Change the cadence, or swap in a manual trigger while you test."
},
"typeVersion": 1
},
{
"id": "e0b3c427-0092-4f92-a092-000000000092",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
220,
60
],
"parameters": {
"color": 7,
"width": 240,
"height": 240,
"content": "### 2. Your input\nRoles to watch, location, an optional company, and the lookback window. This is the only node you edit."
},
"typeVersion": 1
},
{
"id": "e0b3c427-0093-4f93-a093-000000000093",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
480,
60
],
"parameters": {
"color": 7,
"width": 240,
"height": 240,
"content": "### 3. Fan out\nTurns your role list into one LinkedIn job search each."
},
"typeVersion": 1
},
{
"id": "e0b3c427-0094-4f94-a094-000000000094",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
740,
60
],
"parameters": {
"color": 7,
"width": 240,
"height": 240,
"content": "### 4. Search\nRuns the LinkedIn Jobs API actor for one role. Add your Apify credential here."
},
"typeVersion": 1
},
{
"id": "e0b3c427-0095-4f95-a095-000000000095",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1000,
60
],
"parameters": {
"color": 7,
"width": 240,
"height": 240,
"content": "### 5. Clean\nKeeps only real job rows, so an upstream error never reaches the sheet."
},
"typeVersion": 1
},
{
"id": "e0b3c427-0096-4f96-a096-000000000096",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1260,
60
],
"parameters": {
"color": 7,
"width": 240,
"height": 240,
"content": "### 6. Filter and shape\nDrops postings older than your window and works out how many days old each one is."
},
"typeVersion": 1
},
{
"id": "e0b3c427-0097-4f97-a097-000000000097",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
1520,
60
],
"parameters": {
"color": 7,
"width": 240,
"height": 240,
"content": "### 7. Deliver\nPick your spreadsheet and tab. Columns map automatically."
},
"typeVersion": 1
},
{
"id": "e0b3c427-0080-4f80-a080-000000000080",
"name": "Sticky Note8",
"type": "n8n-nodes-base.stickyNote",
"position": [
220,
860
],
"parameters": {
"color": 7,
"width": 400,
"height": 240,
"content": "### Watch an account, not just a role\nLeave the roles broad and set `company` to a target account. You then get everything that company is hiring for, which is the clearest public read on where their budget is going."
},
"typeVersion": 1
},
{
"id": "e0b3c427-0081-4f81-a081-000000000081",
"name": "Sticky Note9",
"type": "n8n-nodes-base.stickyNote",
"position": [
660,
860
],
"parameters": {
"color": 7,
"width": 400,
"height": 240,
"content": "### Self-hosting n8n?\nThere is also a dedicated community node for this actor: `n8n-nodes-linkedin-jobs-api` on npm."
},
"typeVersion": 1
}
],
"settings": {
"executionOrder": "v1"
},
"connections": {
"One search per role": {
"main": [
[
{
"node": "Search LinkedIn jobs",
"type": "main",
"index": 0
}
]
]
},
"Every Monday morning": {
"main": [
[
{
"node": "Set your hiring signal watch",
"type": "main",
"index": 0
}
]
]
},
"Keep recent postings": {
"main": [
[
{
"node": "Append hiring signals to sheet",
"type": "main",
"index": 0
}
]
]
},
"Search LinkedIn jobs": {
"main": [
[
{
"node": "Keep rows that are real jobs",
"type": "main",
"index": 0
}
]
]
},
"Keep rows that are real jobs": {
"main": [
[
{
"node": "Keep recent postings",
"type": "main",
"index": 0
}
]
]
},
"Set your hiring signal watch": {
"main": [
[
{
"node": "One search per role",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs every Monday morning, searches LinkedIn job listings via the Apify LinkedIn Jobs API for a list of target roles (and optional company/location), filters to recent postings, and appends the results as rows in a Google Sheets spreadsheet. Runs every Monday…
Source: https://n8n.io/workflows/17781/ — 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.
> 📊 See real sample data this workflow collects: > View Sample Google Sheet →
Automate your LinkedIn outreach with this n8n workflow powered by Linked API. Sends connection requests, monitors acceptance, delivers your pre-written messages, and follows up automatically – all tra
This template is ideal for sales teams, recruiters, business development professionals, and relationship managers who need to monitor changes in their network's LinkedIn profiles. Perfect for agencies
This workflow runs daily and reads a Google Sheets content calendar to auto-publish scheduled posts to Instagram and Facebook via the Meta Graph API, then posts the same content to LinkedIn and update
Save time - Eliminate manual LinkedIn posting and content scheduling tasks Stay consistent - Automated daily posting keeps your LinkedIn profile active and engaging Keep control - Preview every post b